Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 27b296c9d2 | |||
| 006c0139be | |||
| 63119d3ff3 | |||
| 5859e9c595 | |||
| d0220ceb7f | |||
| 064a6ff588 | |||
| 9a269bfe0e | |||
| c44b933a83 | |||
| 1f5e9fc502 | |||
| 08231074b9 | |||
| c6d801750e | |||
| 2cf49a8db4 | |||
| 4fba8a8594 | |||
| 76365e3a07 | |||
| e6a38af241 | |||
| a4ad49c1a7 | |||
| 0ac1046138 | |||
| eef9c6f562 | |||
| cf18d869e0 | |||
| 4017429835 | |||
| 615f14d614 |
+27
-17
@@ -19,9 +19,11 @@ set (PROJECT_NAME cameradar)
|
||||
|
||||
project (${PROJECT_NAME})
|
||||
|
||||
set (${PROJECT_NAME}_VERSION_MAJOR 0)
|
||||
set (${PROJECT_NAME}_VERSION_MINOR 1)
|
||||
set (${PROJECT_NAME}_VERSION_PATCH 0)
|
||||
message ("Here")
|
||||
|
||||
set (${PROJECT_NAME}_VERSION_MAJOR 1)
|
||||
set (${PROJECT_NAME}_VERSION_MINOR 0)
|
||||
set (${PROJECT_NAME}_VERSION_PATCH 4)
|
||||
set (${PROJECT_NAME}_SUFFIX "-beta")
|
||||
set (${PROJECT_NAME}_VERSION "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}.${${PROJECT_NAME}_VERSION_PATCH}${${PROJECT_NAME}_SUFFIX}")
|
||||
|
||||
@@ -83,29 +85,37 @@ configure_file (
|
||||
"${PROJECT_BINARY_DIR}/version.h"
|
||||
)
|
||||
|
||||
include_directories (
|
||||
"cameradar_standalone/include"
|
||||
"deps/jsoncpp/src/deps.jsoncpp/include"
|
||||
"deps/boost/src/deps.boost/include"
|
||||
# add all deps libraries to the link directories path
|
||||
link_directories (
|
||||
# third party libraries
|
||||
"deps/jsoncpp/src/deps.jsoncpp/src/lib_json"
|
||||
"deps/boost/src/deps.boost/libs"
|
||||
"deps/mysql-connector/lib"
|
||||
)
|
||||
|
||||
include_directories (
|
||||
"cameradar_standalone/include"
|
||||
"deps/jsoncpp/src/deps.jsoncpp/include"
|
||||
"deps/boost/src/deps.boost/include"
|
||||
"deps/mysql-connector/include"
|
||||
)
|
||||
|
||||
set (${CAMERADAR_BINARIES} "")
|
||||
set (${CAMERADAR_LIBRARIES} "")
|
||||
|
||||
#build cache managers
|
||||
add_subdirectory (deps)
|
||||
message ("Debug")
|
||||
add_subdirectory (cameradar_standalone)
|
||||
add_subdirectory (cache_managers)
|
||||
|
||||
set (${CAMERADAR_BINARIES} "")
|
||||
install (PROGRAMS ${CAMERADAR_BINARIES} DESTINATION bin)
|
||||
|
||||
install (FILES ${CAMERADAR_CACHE_MANAGERS} DESTINATION cache_managers)
|
||||
|
||||
set (${CAMERADAR_LIBRARIES} "")
|
||||
list (APPEND CAMERADAR_LIBRARIES ${CAMERADAR_INSTALL_DEPENDENCIES} ${CAMERADAR_LIBRARIES})
|
||||
|
||||
install (PROGRAMS ${CAMERADAR_BINARIES} DESTINATION bin)
|
||||
install (FILES ${CAMERADAR_CACHE_MANAGERS} DESTINATION cache_managers)
|
||||
install (FILES ${CAMERADAR_LIBRARIES} DESTINATION libraries)
|
||||
|
||||
install (DIRECTORY ${CMAKE_SOURCE_DIR}/deps/licenses DESTINATION libraries)
|
||||
|
||||
|
||||
# cpack configuration
|
||||
include (InstallRequiredSystemLibraries)
|
||||
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "cameradar")
|
||||
@@ -115,8 +125,8 @@ set (CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}_${${PROJECT_NAME}_VERSION}_${CMAKE
|
||||
set (CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
|
||||
set (CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
|
||||
set (CPACK_PACKAGE_VERSION_MAJOR "0")
|
||||
set (CPACK_PACKAGE_VERSION_MINOR "1")
|
||||
set (CPACK_PACKAGE_VERSION_PATCH "0")
|
||||
set (CPACK_PACKAGE_VERSION_MINOR "2")
|
||||
set (CPACK_PACKAGE_VERSION_PATCH "2")
|
||||
set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}_${${PROJECT_NAME}_VERSION}")
|
||||
set (CPACK_GENERATOR "TGZ")
|
||||
set (CPACK_SOURCE_GENERATOR "TGZ")
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
## An RTSP surveillance camera access multitool
|
||||
|
||||
[](#license)
|
||||
[](https://github.com/EtixLabs/cameradar/releases/latest)
|
||||
|
||||
|
||||
#### Cameradar allows you to:
|
||||
|
||||
* **Detect open RTSP hosts** on any accessible subnetwork
|
||||
@@ -38,15 +42,15 @@ Of course, you can also call for individual tasks if you plug in a Database to C
|
||||
|
||||
## Quick install
|
||||
|
||||
The quick install uses docker to build Cameradar without polluting your machine with dependencies and makes it easy to deploy Cameradar in a few commands.
|
||||
The quick install uses docker to build Cameradar without polluting your machine with dependencies and makes it easy to deploy Cameradar in a few commands. **However, it may require networking knowledge, as your docker containers will need access to the cameras subnetwork.**
|
||||
|
||||
### Dependencies
|
||||
|
||||
The only dependencies are `docker` and `docker-compose`.
|
||||
The only dependencies are `docker`, `docker-tools`, `git` and `make`.
|
||||
|
||||
### Five steps guide
|
||||
|
||||
1. `https://github.com/EtixLabs/cameradar.git`
|
||||
1. `git clone https://github.com/EtixLabs/cameradar.git`
|
||||
2. Go into the Cameradar repository, then to the `deployment` directory
|
||||
3. Tweak the `conf/cameradar.conf.json` as you need (see [the onfiguration guide here](#configuration) for more information)
|
||||
4. Run `docker-compose build cameradar` to build the cameradar container
|
||||
@@ -63,15 +67,17 @@ The manual installation is recommended if you want to tweak Cameradar and quickl
|
||||
To install Cameradar you will need these packages
|
||||
|
||||
* cmake (`cmake`)
|
||||
* git (`git`)
|
||||
* gstreamer1.x (`libgstreamer1.0-dev`)
|
||||
* ffmpeg (`ffmpeg`)
|
||||
* boost (`libboost-all-dev`)
|
||||
* libcurl (`libcurl4-openssl-dev`)
|
||||
|
||||
### Steps
|
||||
|
||||
The simplest way would be to follow these steps :
|
||||
|
||||
1. `https://github.com/EtixLabs/cameradar.git`
|
||||
1. `git clone https://github.com/EtixLabs/cameradar.git`
|
||||
2. Go into the Cameradar repository, create a directory named `build` and go in it
|
||||
3. In the build directory, run `cmake ..` This will generate the Makefiles you need to build Cameradar
|
||||
4. Run the command `make`
|
||||
@@ -100,6 +106,13 @@ The only dependencies are `docker` and `docker-compose`.
|
||||
Here is the basic content of the configuration file with simple placeholders :
|
||||
```json
|
||||
{
|
||||
"mysql_db" : {
|
||||
"host" : "MYSQL_SERVER_IP_ADDRESS",
|
||||
"port" : MYSQL_SERVER_PORT,
|
||||
"user": "root",
|
||||
"password": "root",
|
||||
"db_name": "cmrdr"
|
||||
},
|
||||
"subnets" : "SUBNET1,SUBNET2,SUBNET3,[...]",
|
||||
"ports" : "PORT1,PORT2,[...]",
|
||||
"rtsp_url_file" : "conf/url.json",
|
||||
@@ -115,7 +128,8 @@ The subnetworks should be passed separated by commas only, and their subnet form
|
||||
"subnets" : "172.100.16.0/24,172.100.17.0/24,localhost,192.168.1.13"
|
||||
```
|
||||
|
||||
The **RTSP ports for most cameras are 554**, so you should probably specify 554 as one of the ports you scan. Not giving any ports in the configuration will scan every port of every host found on the subnetworks..How is formatted Cameradar's result
|
||||
The **RTSP ports for most cameras are 554**, so you should probably specify 554 as one of the ports you scan. Not giving any ports in the configuration will scan every port of every host found on the subnetworks.
|
||||
|
||||
You **can use your own files for the ids and routes dictionaries** used to bruteforce the cameras, but the Cameradar repo already gives you a good base that works with most cameras.
|
||||
|
||||
The thumbnail storage path should be a **valid and accessible directory** in which the thumbnails will be stored.
|
||||
@@ -206,7 +220,7 @@ If you have other cool ideas, feel free to share them with me at brendan.leglaun
|
||||
## Next improvements
|
||||
|
||||
- [x] Add a docker deployment to avoid the current deps hell
|
||||
- [ ] Development of a MySQL cache manager
|
||||
- [x] Development of a MySQL cache manager
|
||||
- [ ] Development of a JSON file cache manager
|
||||
- [ ] Development of an XML file cache manager
|
||||
|
||||
|
||||
@@ -19,5 +19,6 @@ cmake_policy(SET CMP0042 NEW)
|
||||
set (LIBRARY_OUTPUT_PATH ${CAMERADAR_CACHE_MANAGER_OUTPUT_PATH})
|
||||
|
||||
add_subdirectory(dumb_cache_manager)
|
||||
add_subdirectory(mysql_cache_manager)
|
||||
|
||||
set (CAMERADAR_CACHE_MANAGERS ${CAMERADAR_CACHE_MANAGERS} PARENT_SCOPE)
|
||||
|
||||
@@ -20,7 +20,6 @@ project(dumb_cache_manager CXX)
|
||||
find_package(PkgConfig)
|
||||
|
||||
include_directories (${PROJECT_SOURCE_DIR}/include ${CAMERADAR_INCLUDES})
|
||||
message("${CAMERADAR_INCLUDES}")
|
||||
|
||||
include (find_sources)
|
||||
find_sources ("src" "include")
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <cachemanager.h>
|
||||
#include <stream_model.h>
|
||||
#include <configuration.h>
|
||||
#include <logger.h>
|
||||
#include <stream_model.h>
|
||||
#include <vector>
|
||||
|
||||
namespace etix {
|
||||
namespace cameradar {
|
||||
@@ -42,9 +42,9 @@ public:
|
||||
|
||||
void update_stream(const etix::cameradar::stream_model& newmodel);
|
||||
|
||||
std::vector<etix::cameradar::stream_model> get_streams() const;
|
||||
std::vector<etix::cameradar::stream_model> get_streams();
|
||||
|
||||
std::vector<etix::cameradar::stream_model> get_valid_streams() const;
|
||||
std::vector<etix::cameradar::stream_model> get_valid_streams();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,29 +53,32 @@ dumb_cache_manager::set_streams(std::vector<etix::cameradar::stream_model> model
|
||||
//! Inserts a single stream to the cache
|
||||
void
|
||||
dumb_cache_manager::update_stream(const etix::cameradar::stream_model& newmodel) {
|
||||
for (auto& it : this->streams) {
|
||||
if (it.address == newmodel.address && it.port == newmodel.port) { it = newmodel; }
|
||||
for (auto& stream : this->streams) {
|
||||
if (stream.address == newmodel.address && stream.port == newmodel.port) {
|
||||
stream = newmodel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//! Gets all cached streams
|
||||
std::vector<etix::cameradar::stream_model>
|
||||
dumb_cache_manager::get_streams() const {
|
||||
dumb_cache_manager::get_streams() {
|
||||
std::vector<stream_model> ret;
|
||||
for (const auto& it : this->streams) {
|
||||
if (not it.service_name.compare("rtsp") && not it.state.compare("open")) ret.push_back(it);
|
||||
for (const auto& stream : this->streams) {
|
||||
if (not stream.service_name.compare("rtsp") && not stream.state.compare("open"))
|
||||
ret.push_back(stream);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
//! Gets all valid streams
|
||||
std::vector<etix::cameradar::stream_model>
|
||||
dumb_cache_manager::get_valid_streams() const {
|
||||
dumb_cache_manager::get_valid_streams() {
|
||||
std::vector<stream_model> ret;
|
||||
for (const auto& it : this->streams) {
|
||||
if ((not it.service_name.compare("rtsp") && not it.state.compare("open")) && it.ids_found &&
|
||||
it.path_found)
|
||||
ret.push_back(it);
|
||||
for (const auto& stream : this->streams) {
|
||||
if ((not stream.service_name.compare("rtsp") && not stream.state.compare("open")) &&
|
||||
stream.ids_found && stream.path_found)
|
||||
ret.push_back(stream);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
## Copyright 2016 Etix Labs
|
||||
##
|
||||
## Licensed under the Apache License, Version 2.0 (the "License");
|
||||
## you may not use this file except in compliance with the License.
|
||||
## You may obtain a copy of the License at
|
||||
##
|
||||
## http://www.apache.org/licenses/LICENSE-2.0
|
||||
##
|
||||
## Unless required by applicable law or agreed to in writing, software
|
||||
## distributed under the License is distributed on an "AS IS" BASIS,
|
||||
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
## See the License for the specific language governing permissions and
|
||||
## limitations under the License.
|
||||
|
||||
cmake_minimum_required (VERSION 2.8.1)
|
||||
cmake_policy(SET CMP0042 NEW)
|
||||
|
||||
project(mysql_cache_manager CXX)
|
||||
|
||||
find_package(PkgConfig)
|
||||
|
||||
include_directories (${PROJECT_SOURCE_DIR}/include ${CAMERADAR_INCLUDES})
|
||||
|
||||
include (find_sources)
|
||||
find_sources ("src" "include")
|
||||
|
||||
add_library (mysql_cache_manager SHARED ${SOURCES})
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined")
|
||||
target_link_libraries (mysql_cache_manager jsoncpp mysqlcppconn pthread)
|
||||
|
||||
set (CACHE_MANAGER_NAME ${CAMERADAR_CACHE_MANAGER_OUTPUT_PATH}/${CMAKE_SHARED_LIBRARY_PREFIX}mysql_cache_manager${CMAKE_SHARED_LIBRARY_SUFFIX})
|
||||
list (APPEND CAMERADAR_CACHE_MANAGERS ${CACHE_MANAGER_NAME})
|
||||
set (CAMERADAR_CACHE_MANAGERS ${CAMERADAR_CACHE_MANAGERS} PARENT_SCOPE)
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright 2016 Etix Labs
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cppconn/resultset.h> // for ResultSet
|
||||
#include <mutex> // for mutex
|
||||
#include <stdbool.h> // for bool, false
|
||||
#include <string> // for string
|
||||
#include <utility> // for pair, make_pair
|
||||
|
||||
#include "query_result.h"
|
||||
|
||||
namespace sql {
|
||||
class Connection;
|
||||
class Driver;
|
||||
class ResultSet;
|
||||
}
|
||||
|
||||
namespace etix {
|
||||
|
||||
namespace cameradar {
|
||||
|
||||
namespace mysql {
|
||||
|
||||
//! MySQL Database connection handling
|
||||
//! Abstracts all connection to the database
|
||||
class db_connection {
|
||||
private:
|
||||
static const std::string create_database_query;
|
||||
|
||||
//! SQL driver
|
||||
sql::Driver* driver = nullptr;
|
||||
//! SQL connection
|
||||
sql::Connection* connection = nullptr;
|
||||
std::mutex access_mtx;
|
||||
bool connected = false;
|
||||
|
||||
std::string db_name;
|
||||
|
||||
//! Create the database if it doesn't exist at connector launch
|
||||
empty_result create_database(void);
|
||||
|
||||
public:
|
||||
db_connection(void);
|
||||
~db_connection(void);
|
||||
|
||||
//! Try to connect to the database
|
||||
std::pair<bool, std::string> connect(const std::string& host,
|
||||
const std::string& user,
|
||||
const std::string& pass,
|
||||
const std::string& db_name,
|
||||
bool create_db_if_not_exist = true);
|
||||
|
||||
//! Execute a MySQL command
|
||||
empty_result execute(const std::string& request);
|
||||
|
||||
//! Execute a query
|
||||
query_result<sql::ResultSet*> query(const std::string& query);
|
||||
|
||||
bool is_connected();
|
||||
|
||||
//! Return db_name
|
||||
const std::string&
|
||||
get_db_name(void) const {
|
||||
return this->db_name;
|
||||
}
|
||||
};
|
||||
|
||||
} // mysql
|
||||
|
||||
} // cameradar
|
||||
|
||||
} // etix
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2016 Etix Labs
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cachemanager.h>
|
||||
#include <configuration.h>
|
||||
#include <db_conn.h>
|
||||
#include <fmt.h>
|
||||
#include <logger.h>
|
||||
#include <stream_model.h>
|
||||
#include <vector>
|
||||
|
||||
namespace etix {
|
||||
|
||||
namespace cameradar {
|
||||
|
||||
struct mysql_configuration {
|
||||
unsigned int port;
|
||||
std::string host;
|
||||
std::string db_name;
|
||||
std::string user;
|
||||
std::string password;
|
||||
|
||||
mysql_configuration() = default;
|
||||
|
||||
mysql_configuration(unsigned int port,
|
||||
const std::string& host,
|
||||
const std::string& db_name,
|
||||
const std::string& user = "",
|
||||
const std::string& password = "")
|
||||
: port(port), host(host), db_name(db_name), user(user), password(password) {}
|
||||
};
|
||||
|
||||
class mysql_cache_manager : public cache_manager_base {
|
||||
private:
|
||||
static const std::string name;
|
||||
std::vector<etix::cameradar::stream_model> streams;
|
||||
std::shared_ptr<etix::cameradar::configuration> configuration;
|
||||
etix::cameradar::mysql_configuration db_conf;
|
||||
etix::cameradar::mysql::db_connection connection;
|
||||
|
||||
static const std::string create_table_query;
|
||||
static const std::string insert_with_id_query;
|
||||
static const std::string exist_query;
|
||||
static const std::string get_results_query;
|
||||
static const std::string update_result_query;
|
||||
|
||||
public:
|
||||
using cache_manager_base::cache_manager_base;
|
||||
~mysql_cache_manager();
|
||||
|
||||
// Specific to MySQL
|
||||
bool execute_query(const std::string& query);
|
||||
|
||||
const std::string& get_name() const override;
|
||||
static const std::string& static_get_name();
|
||||
bool load_mysql_conf(std::shared_ptr<etix::cameradar::configuration> configuration);
|
||||
bool configure(std::shared_ptr<etix::cameradar::configuration> configuration) override;
|
||||
|
||||
void set_streams(std::vector<etix::cameradar::stream_model> model);
|
||||
|
||||
void update_stream(const etix::cameradar::stream_model& newmodel);
|
||||
|
||||
std::vector<etix::cameradar::stream_model> get_streams();
|
||||
|
||||
std::vector<etix::cameradar::stream_model> get_valid_streams();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2016 Etix Labs
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace etix {
|
||||
|
||||
namespace cameradar {
|
||||
|
||||
namespace mysql {
|
||||
|
||||
enum class execute_result { success, not_found, no_row_updated, sql_error, error };
|
||||
|
||||
//! Wrapper of a DB query result
|
||||
//! Templated on the data type we want to return (list<model>, bool, whatever)
|
||||
template <typename DataType>
|
||||
struct query_result {
|
||||
DataType data;
|
||||
execute_result state;
|
||||
std::string error_msg;
|
||||
|
||||
inline bool
|
||||
success(void) const {
|
||||
return state == execute_result::success;
|
||||
}
|
||||
inline bool
|
||||
error(void) const {
|
||||
return not success();
|
||||
}
|
||||
};
|
||||
|
||||
//! Empty query result for when we just want to return the status
|
||||
//! of the request with no associated data
|
||||
template <>
|
||||
struct query_result<void> {
|
||||
execute_result state;
|
||||
std::string error_msg;
|
||||
|
||||
inline bool
|
||||
success(void) const {
|
||||
return state == execute_result::success;
|
||||
}
|
||||
inline bool
|
||||
error(void) const {
|
||||
return not success();
|
||||
}
|
||||
};
|
||||
typedef query_result<void> empty_result;
|
||||
|
||||
} //! mysql
|
||||
|
||||
} //! cameradar
|
||||
|
||||
} //! etix
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright 2016 Etix Labs
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "db_conn.h" // for db_connection
|
||||
#include "cppconn/connection.h" // for Connection
|
||||
#include "query_result.h" // for queries
|
||||
#include <cppconn/driver.h> // for get_driver_instance, etc
|
||||
#include <cppconn/exception.h> // for SQLException
|
||||
#include <cppconn/statement.h> // for Statement
|
||||
#include <fmt.h> // for fmt
|
||||
#include <logger.h> // for LOG_
|
||||
|
||||
namespace etix {
|
||||
|
||||
namespace cameradar {
|
||||
|
||||
namespace mysql {
|
||||
|
||||
const std::string db_connection::create_database_query = "CREATE DATABASE IF NOT EXISTS %s";
|
||||
|
||||
db_connection::db_connection() : connected(false) {}
|
||||
|
||||
db_connection::~db_connection() { delete this->connection; }
|
||||
|
||||
std::pair<bool, std::string>
|
||||
db_connection::connect(const std::string& host,
|
||||
const std::string& user,
|
||||
const std::string& pass,
|
||||
const std::string& db_name,
|
||||
bool create_db_if_not_exist) {
|
||||
this->db_name = db_name;
|
||||
|
||||
try {
|
||||
this->driver = get_driver_instance();
|
||||
if (this->driver == nullptr) {
|
||||
return std::make_pair(false, "Cannot instantiate sql_driver");
|
||||
}
|
||||
this->connection = driver->connect(host, user, pass);
|
||||
if (this->connection == nullptr) return std::make_pair(false, "Cannot connect to mysql");
|
||||
|
||||
this->connected = true;
|
||||
if (create_db_if_not_exist) {
|
||||
auto cdb = this->create_database();
|
||||
if (cdb.state == mysql::execute_result::sql_error) { return { false, cdb.error_msg }; }
|
||||
this->connection->setSchema(db_name);
|
||||
}
|
||||
} catch (sql::SQLException& e) {
|
||||
this->connected = false;
|
||||
return { false, e.what() };
|
||||
}
|
||||
|
||||
return std::make_pair(true, "");
|
||||
}
|
||||
|
||||
empty_result
|
||||
db_connection::execute(const std::string& request) {
|
||||
std::lock_guard<std::mutex> lock(this->access_mtx);
|
||||
|
||||
sql::Statement* stmt = nullptr;
|
||||
empty_result return_value = { execute_result::success, "" };
|
||||
|
||||
if (!this->is_connected()) {
|
||||
return { execute_result::sql_error, "Error, not connected to MySQL database" };
|
||||
}
|
||||
|
||||
try {
|
||||
stmt = this->connection->createStatement();
|
||||
stmt->execute(request);
|
||||
if (stmt->getUpdateCount() == 0) {
|
||||
return_value = { execute_result::no_row_updated, "No row updated" };
|
||||
}
|
||||
} catch (sql::SQLException& e) { return_value = { execute_result::sql_error, e.what() }; }
|
||||
if (stmt) { delete stmt; }
|
||||
|
||||
return return_value;
|
||||
}
|
||||
|
||||
query_result<sql::ResultSet*>
|
||||
db_connection::query(const std::string& query) {
|
||||
std::lock_guard<std::mutex> lock(this->access_mtx);
|
||||
|
||||
sql::Statement* stmt = nullptr;
|
||||
query_result<sql::ResultSet*> return_value = { nullptr, execute_result::success, "" };
|
||||
|
||||
if (!this->is_connected()) {
|
||||
return { nullptr, execute_result::sql_error, "Error, not connected to MySQL database" };
|
||||
}
|
||||
|
||||
try {
|
||||
stmt = this->connection->createStatement();
|
||||
return_value = { stmt->executeQuery(query), execute_result::success, "" };
|
||||
} catch (sql::SQLException& e) {
|
||||
return_value = { nullptr, execute_result::sql_error, e.what() };
|
||||
}
|
||||
|
||||
if (stmt) { delete stmt; }
|
||||
|
||||
return return_value;
|
||||
}
|
||||
|
||||
bool
|
||||
db_connection::is_connected() {
|
||||
if (this->connection == nullptr) return false;
|
||||
|
||||
// check if our connection is always valid
|
||||
if (this->connection->isClosed() || not this->connection->isValid()) {
|
||||
LOG_INFO_("MySQL database connection is either closed or invalid, try to reconnect.",
|
||||
"db_connection");
|
||||
this->connection->reconnect();
|
||||
if (this->connection->isClosed() || not this->connection->isValid()) {
|
||||
this->connected = false;
|
||||
LOG_ERR_("Unable to reconnect to MySQL.", "db_connection");
|
||||
}
|
||||
}
|
||||
return this->connected;
|
||||
}
|
||||
|
||||
empty_result
|
||||
db_connection::create_database() {
|
||||
auto query = tool::fmt(this->create_database_query, this->db_name.c_str());
|
||||
return this->execute(query);
|
||||
}
|
||||
|
||||
} // mysql
|
||||
|
||||
} // cameradar
|
||||
|
||||
} // etix
|
||||
@@ -0,0 +1,274 @@
|
||||
// Copyright 2016 Etix Labs
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <mysql_cache_manager.h>
|
||||
|
||||
/* DATA FORMAT
|
||||
**
|
||||
** Example :
|
||||
**
|
||||
** "address" : "173.16.100.45",
|
||||
** "ids_found" : true,
|
||||
** "password" : "123456",
|
||||
** "path_found" : true,
|
||||
** "port" : 554,
|
||||
** "product" : "Vivotek FD9381-HTV",
|
||||
** "protocol" : "tcp",
|
||||
** "route" : "/live.sdp",
|
||||
** "service_name" : "rtsp",
|
||||
** "state" : "open",
|
||||
** "thumbnail_path" : "/tmp/127.0.0.1/1463735257.jpg",
|
||||
** "username" : "admin"
|
||||
**
|
||||
*/
|
||||
|
||||
namespace etix {
|
||||
|
||||
namespace cameradar {
|
||||
|
||||
const std::string mysql_cache_manager::create_table_query =
|
||||
"CREATE TABLE IF NOT EXISTS `results` ("
|
||||
"`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, "
|
||||
"`address` tinytext NOT NULL, "
|
||||
"`password` tinytext NOT NULL, "
|
||||
"`product` tinytext NOT NULL, "
|
||||
"`protocol` tinytext NOT NULL, "
|
||||
"`route` tinytext NOT NULL, "
|
||||
"`service_name` tinytext NOT NULL, "
|
||||
"`state` tinytext NOT NULL, "
|
||||
"`thumbnail_path` tinytext NOT NULL, "
|
||||
"`username` tinytext NOT NULL, "
|
||||
"`port` int(11) UNSIGNED NOT NULL, "
|
||||
"`ids_found` tinytext NOT NULL, "
|
||||
"`path_found` tinytext NOT NULL, "
|
||||
"PRIMARY KEY (`id`));";
|
||||
|
||||
const std::string mysql_cache_manager::insert_with_id_query =
|
||||
"INSERT INTO `%s`.`results`"
|
||||
" (`address`, `password`, `product`, `protocol`, `route`, `service_name`, `state`, "
|
||||
"`thumbnail_path`, `username`, `port`, `ids_found`, `path_found`)"
|
||||
" VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')";
|
||||
|
||||
const std::string mysql_cache_manager::update_result_query =
|
||||
"UPDATE `%s`.`results` SET"
|
||||
" `results`.`address` = '%s',"
|
||||
" `results`.`password` = '%s',"
|
||||
" `results`.`product` = '%s',"
|
||||
" `results`.`protocol` = '%s',"
|
||||
" `results`.`route` = '%s',"
|
||||
" `results`.`service_name` = '%s',"
|
||||
" `results`.`state` = '%s',"
|
||||
" `results`.`thumbnail_path` = '%s',"
|
||||
" `results`.`username` = '%s',"
|
||||
" `results`.`port` = '%s',"
|
||||
" `results`.`ids_found` = '%s',"
|
||||
" `results`.`path_found` = '%s'"
|
||||
" WHERE `results`.`address` LIKE '%s'";
|
||||
|
||||
const std::string mysql_cache_manager::exist_query =
|
||||
"SELECT * FROM `%s`.`results` WHERE `results`.`address` = '%s'";
|
||||
|
||||
const std::string mysql_cache_manager::get_results_query = "SELECT * FROM `%s`.`results`";
|
||||
|
||||
const std::string mysql_cache_manager::name = "mysql-cache-manager";
|
||||
|
||||
mysql_cache_manager::~mysql_cache_manager() {}
|
||||
|
||||
const std::string&
|
||||
mysql_cache_manager::get_name() const {
|
||||
return mysql_cache_manager::static_get_name();
|
||||
}
|
||||
|
||||
const std::string&
|
||||
mysql_cache_manager::static_get_name() {
|
||||
return mysql_cache_manager::name;
|
||||
}
|
||||
|
||||
bool
|
||||
mysql_cache_manager::configure(std::shared_ptr<etix::cameradar::configuration> configuration) {
|
||||
return this->load_mysql_conf(configuration);
|
||||
}
|
||||
|
||||
bool
|
||||
mysql_cache_manager::execute_query(const std::string& query) {
|
||||
auto check_err = [](const auto& res) {
|
||||
if (res.state == mysql::execute_result::sql_error) {
|
||||
LOG_WARN_(res.error_msg, "mysql_cache_manager");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
return check_err(this->connection.execute(query));
|
||||
}
|
||||
|
||||
bool
|
||||
mysql_cache_manager::load_mysql_conf(
|
||||
std::shared_ptr<etix::cameradar::configuration> configuration) {
|
||||
this->configuration = configuration;
|
||||
|
||||
try {
|
||||
this->db_conf.host = configuration->raw_conf["mysql_db"]["host"].asString();
|
||||
this->db_conf.port = configuration->raw_conf["mysql_db"]["port"].asUInt();
|
||||
this->db_conf.user = configuration->raw_conf["mysql_db"]["user"].asString();
|
||||
this->db_conf.password = configuration->raw_conf["mysql_db"]["password"].asString();
|
||||
this->db_conf.db_name = configuration->raw_conf["mysql_db"]["db_name"].asString();
|
||||
} catch (std::exception& e) {
|
||||
LOG_ERR_("Configuration of the MySQL db failed : " + std::string(e.what()),
|
||||
"mysql_cache_manager");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not this->connection
|
||||
.connect(db_conf.host + ":" + std::to_string(db_conf.port),
|
||||
db_conf.user,
|
||||
db_conf.password,
|
||||
db_conf.db_name)
|
||||
.first) {
|
||||
LOG_ERR_("Configuration of the MySQL DB failed", "mysql_cache_manager");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tries to create the Result table in the DB and returns the success state
|
||||
return (execute_query(create_table_query));
|
||||
}
|
||||
|
||||
//! Replaces all cached streams by the content of the vector given as
|
||||
//! parameter
|
||||
void
|
||||
mysql_cache_manager::set_streams(std::vector<etix::cameradar::stream_model> models) {
|
||||
LOG_DEBUG_("Beginning stream list DB insertion", "mysql_cache_manager");
|
||||
for (const auto& model : models) {
|
||||
if (!model.service_name.compare("rtsp") && !model.state.compare("open")) {
|
||||
auto query = tool::fmt(
|
||||
this->exist_query, this->connection.get_db_name().c_str(), model.address.c_str());
|
||||
auto result = this->connection.query(query);
|
||||
// If an entry already exists for this address in the database,
|
||||
// no need to insert it.
|
||||
|
||||
// TODO : Update an entry if it already exists.
|
||||
|
||||
if (result.data->next()) return;
|
||||
|
||||
query = tool::fmt(this->insert_with_id_query,
|
||||
this->connection.get_db_name().c_str(),
|
||||
model.address.c_str(),
|
||||
model.password.c_str(),
|
||||
model.product.c_str(),
|
||||
model.protocol.c_str(),
|
||||
model.route.c_str(),
|
||||
model.service_name.c_str(),
|
||||
model.state.c_str(),
|
||||
model.thumbnail_path.c_str(),
|
||||
model.username.c_str(),
|
||||
std::to_string(model.port).c_str(),
|
||||
std::to_string(model.ids_found).c_str(),
|
||||
std::to_string(model.path_found).c_str());
|
||||
execute_query(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//! Inserts a single stream to the cache
|
||||
void
|
||||
mysql_cache_manager::update_stream(const etix::cameradar::stream_model& model) {
|
||||
auto query = tool::fmt(this->update_result_query,
|
||||
this->connection.get_db_name().c_str(),
|
||||
model.address.c_str(),
|
||||
model.password.c_str(),
|
||||
model.product.c_str(),
|
||||
model.protocol.c_str(),
|
||||
model.route.c_str(),
|
||||
model.service_name.c_str(),
|
||||
model.state.c_str(),
|
||||
model.thumbnail_path.c_str(),
|
||||
model.username.c_str(),
|
||||
std::to_string(model.port).c_str(),
|
||||
std::to_string(model.ids_found).c_str(),
|
||||
std::to_string(model.path_found).c_str(),
|
||||
model.address.c_str());
|
||||
execute_query(query);
|
||||
}
|
||||
|
||||
//! Gets all cached streams
|
||||
std::vector<etix::cameradar::stream_model>
|
||||
mysql_cache_manager::get_streams() {
|
||||
auto query = tool::fmt(this->get_results_query, this->connection.get_db_name().c_str());
|
||||
auto result = this->connection.query(query);
|
||||
|
||||
if (not result.data) {
|
||||
delete result.data;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<stream_model> lst;
|
||||
while (result.data->next()) {
|
||||
// If it's an open RTSP stream
|
||||
if (not result.data->getString("state").compare("open") &&
|
||||
not result.data->getString("service_name").compare("rtsp")) {
|
||||
stream_model s{
|
||||
result.data->getString("address"), result.data->getUInt("port"),
|
||||
result.data->getString("username"), result.data->getString("password"),
|
||||
result.data->getString("route"), result.data->getString("service_name"),
|
||||
result.data->getString("product"), result.data->getString("protocol"),
|
||||
result.data->getString("state"), result.data->getBoolean("ids_found"),
|
||||
result.data->getBoolean("path_found"), result.data->getString("thumbnail_path")
|
||||
};
|
||||
lst.push_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
delete result.data;
|
||||
return lst;
|
||||
}
|
||||
|
||||
//! Gets all valid streams
|
||||
std::vector<etix::cameradar::stream_model>
|
||||
mysql_cache_manager::get_valid_streams() {
|
||||
auto query = tool::fmt(this->get_results_query, this->connection.get_db_name().c_str());
|
||||
auto result = this->connection.query(query);
|
||||
|
||||
if (not result.data) {
|
||||
delete result.data;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<stream_model> lst;
|
||||
while (result.data->next()) {
|
||||
// If the ID and the Path were found add this stream
|
||||
if (not result.data->getString("ids_found").compare("1") &&
|
||||
not result.data->getString("path_found").compare("1")) {
|
||||
stream_model s{
|
||||
result.data->getString("address"), result.data->getUInt("port"),
|
||||
result.data->getString("username"), result.data->getString("password"),
|
||||
result.data->getString("route"), result.data->getString("service_name"),
|
||||
result.data->getString("product"), result.data->getString("protocol"),
|
||||
result.data->getString("state"), result.data->getBoolean("ids_found"),
|
||||
result.data->getBoolean("path_found"), result.data->getString("thumbnail_path")
|
||||
};
|
||||
lst.push_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
delete result.data;
|
||||
return lst;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
cache_manager_iface*
|
||||
cache_manager_instance_new() {
|
||||
return new mysql_cache_manager();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,13 @@
|
||||
{
|
||||
"subnets" : "172.16.100.13,localhost",
|
||||
"mysql_db" : {
|
||||
"host" : "0.0.0.0",
|
||||
"port" : 3306,
|
||||
"user": "root",
|
||||
"password": "root",
|
||||
"db_name": "cctv_dev"
|
||||
},
|
||||
|
||||
"subnets" : "172.16.100.11",
|
||||
|
||||
// If not specified, will scan all ports (1-65535)
|
||||
"ports" : "554,8554",
|
||||
@@ -9,6 +17,9 @@
|
||||
// You must give an accessible path to an already existing directory
|
||||
"thumbnail_storage_path" : "/tmp",
|
||||
|
||||
"cache_manager_path" : "../cache_managers/dumb_cache_manager",
|
||||
// This is the path that will be used in the Docker container
|
||||
// if you're not familiar with Docker, only change the
|
||||
// cache_manager_name value
|
||||
"cache_manager_path" : "../cache_managers",
|
||||
"cache_manager_name" : "dumb"
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <configuration.h>
|
||||
#include <memory>
|
||||
#include <stream_model.h>
|
||||
#include <configuration.h>
|
||||
#include <vector>
|
||||
|
||||
namespace etix {
|
||||
namespace cameradar {
|
||||
@@ -42,10 +42,10 @@ public:
|
||||
virtual void update_stream(const etix::cameradar::stream_model& newmodel) = 0;
|
||||
|
||||
//! Gets all cached streams
|
||||
virtual std::vector<etix::cameradar::stream_model> get_streams() const = 0;
|
||||
virtual std::vector<etix::cameradar::stream_model> get_streams() = 0;
|
||||
|
||||
//! Gets all valid streams which have been accessed
|
||||
virtual std::vector<etix::cameradar::stream_model> get_valid_streams() const = 0;
|
||||
virtual std::vector<etix::cameradar::stream_model> get_valid_streams() = 0;
|
||||
};
|
||||
|
||||
class cache_manager_base : public cache_manager_iface {
|
||||
@@ -68,10 +68,10 @@ public:
|
||||
virtual void update_stream(const etix::cameradar::stream_model& newmodel) = 0;
|
||||
|
||||
//! Gets all cached streams
|
||||
virtual std::vector<etix::cameradar::stream_model> get_streams() const = 0;
|
||||
virtual std::vector<etix::cameradar::stream_model> get_streams() = 0;
|
||||
|
||||
//! Gets all valid streams which have been accessed
|
||||
virtual std::vector<etix::cameradar::stream_model> get_valid_streams() const = 0;
|
||||
virtual std::vector<etix::cameradar::stream_model> get_valid_streams() = 0;
|
||||
|
||||
//! Get the manager's instance
|
||||
cache_manager_base& get_instance();
|
||||
|
||||
@@ -64,7 +64,7 @@ public:
|
||||
const std::pair<bool, etix::tool::opt_parse>& opts)
|
||||
: busy(false)
|
||||
, current(task::init)
|
||||
, nmap_output("scans/scan" + std::to_string(std::chrono::system_clock::to_time_t(
|
||||
, nmap_output("/tmp/scans/scan" + std::to_string(std::chrono::system_clock::to_time_t(
|
||||
std::chrono::system_clock::now())) +
|
||||
".xml")
|
||||
, conf(conf)
|
||||
|
||||
@@ -14,11 +14,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h> // assert
|
||||
#include <csignal> // sigint
|
||||
#include <iostream> // stc::cout
|
||||
#include <assert.h> // assert
|
||||
|
||||
// To avoid an unused warning for the asserted in handle_signal
|
||||
#define _unused(x) ((void)(x))
|
||||
|
||||
namespace etix {
|
||||
|
||||
namespace cameradar {
|
||||
|
||||
enum class stop_priority { running, stop, force_stop };
|
||||
@@ -30,6 +34,7 @@ public:
|
||||
virtual int
|
||||
handle_signal(int signum) {
|
||||
assert(signum == SIGINT);
|
||||
_unused(signum);
|
||||
std::cout << "\b\b\b\033[K";
|
||||
if (this->ss == stop_priority::running)
|
||||
this->ss = stop_priority::stop;
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <json/value.h>
|
||||
#include <string>
|
||||
|
||||
namespace etix {
|
||||
namespace cameradar {
|
||||
@@ -24,7 +24,7 @@ struct stream_model {
|
||||
// Ex : "172.16.100.113"
|
||||
std::string address;
|
||||
// Ex : 8554
|
||||
unsigned short port;
|
||||
unsigned int port;
|
||||
// Ex : "admin"
|
||||
std::string username = "";
|
||||
// Ex : "123456"
|
||||
|
||||
@@ -14,12 +14,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cameradar_task.h> // task interface
|
||||
#include <boost/algorithm/string/find.hpp> // boost::find
|
||||
#include <iostream> // std::ofstream
|
||||
#include <fstream> // std::ofstream
|
||||
#include <stream_model.h> // data model
|
||||
#include <cachemanager.h> // cacheManager
|
||||
#include <cachemanager.h> // cacheManager
|
||||
#include <cameradar_task.h> // task interface
|
||||
#include <fstream> // std::ofstream
|
||||
#include <iostream> // std::ofstream
|
||||
#include <stream_model.h> // data model
|
||||
|
||||
namespace etix {
|
||||
namespace cameradar {
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <configuration.h> // configuration
|
||||
#include <fstream> // std::ifstream
|
||||
#include <unistd.h> // access, F_OK
|
||||
#include <configuration.h> // configuration
|
||||
|
||||
namespace etix {
|
||||
|
||||
@@ -22,9 +22,9 @@ namespace cameradar {
|
||||
|
||||
const std::string configuration::name_ = "configuration";
|
||||
|
||||
// read a file at the path "path"
|
||||
// if the file is available we return the whole content as an std::string inside
|
||||
// a pair
|
||||
// Read a file at the path "path"
|
||||
// If the file is available we return the whole content as
|
||||
// an std::string inside a pair
|
||||
// otherwise return false and an empty string inside a pair
|
||||
std::pair<bool, std::string>
|
||||
read_file(const std::string& path) {
|
||||
@@ -107,14 +107,6 @@ configuration::load_url() {
|
||||
auto root = Json::Value();
|
||||
auto reader = Json::Reader();
|
||||
reader.parse(content, root);
|
||||
// auto result = tool::json::check_fields(
|
||||
// {{"urls", Json::arrayValue, root["urls"]}}, "general
|
||||
// configuration");
|
||||
|
||||
// if (not result.first) {
|
||||
// LOG_ERR_(result.second, "general configuration");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
for (unsigned int i = 0; i < root["urls"].size(); i++) {
|
||||
if (not root["urls"][i].isString()) {
|
||||
@@ -194,7 +186,6 @@ load(const std::string& path) {
|
||||
}
|
||||
// Deserialize the json to a configuration struct
|
||||
// and return
|
||||
// REPLACE THIS WITH JSONCPP
|
||||
std::pair<bool, configuration> conf = serialize(root);
|
||||
conf.second.raw_conf = root;
|
||||
conf.first &= conf.second.load_url();
|
||||
|
||||
@@ -71,12 +71,15 @@ curl_describe(const std::string& path, bool logs) {
|
||||
curl_easy_cleanup(csession);
|
||||
fclose(protofile);
|
||||
curl_global_cleanup();
|
||||
LOG_DEBUG_("Response code : " + std::to_string(rc), "describe");
|
||||
if (logs) {
|
||||
if (rc != 401 && pos == std::string::npos)
|
||||
// Some cameras return 400 instead of 401, don't know why.
|
||||
// Some cameras timeout and then curl considers the status as 0
|
||||
if (rc != 401 && rc != 400 && rc && pos == std::string::npos)
|
||||
LOG_INFO_("Unprotected camera discovered.", "brutelogs");
|
||||
return ((res == CURLE_OK) && rc != 401);
|
||||
return ((res == CURLE_OK) && rc != 401 && rc != 400 && rc);
|
||||
}
|
||||
return ((res == CURLE_OK) && rc != 404);
|
||||
return ((res == CURLE_OK) && rc != 404 && rc != 400 && rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,20 @@ namespace cameradar {
|
||||
// The main loop of the binary
|
||||
void
|
||||
dispatcher::run() {
|
||||
if (not(*cache)->configure(std::make_shared<configuration>(conf))) {
|
||||
LOG_ERR_(
|
||||
"There was a problem with the cache manager, Cameradar can't work properly without "
|
||||
"cache management",
|
||||
"dispatcher");
|
||||
return;
|
||||
}
|
||||
std::thread worker(&dispatcher::do_stuff, this);
|
||||
using namespace std::chrono_literals;
|
||||
// catch CTRL+C signal
|
||||
|
||||
// Catch CTRL+C signal
|
||||
signal_handler::instance();
|
||||
|
||||
// wait for event or end
|
||||
// Wait for event or end
|
||||
while (signal_handler::instance().should_stop() not_eq stop_priority::stop &&
|
||||
current != task::finished) {
|
||||
std::this_thread::sleep_for(30ms);
|
||||
@@ -36,7 +44,7 @@ dispatcher::run() {
|
||||
LOG_INFO_("Press CTRL+C again to force stop", "dispatcher");
|
||||
}
|
||||
|
||||
// waiting for task to cleanup / force stop command
|
||||
// Waiting for task to cleanup / force stop command
|
||||
while ((signal_handler::instance().should_stop() not_eq stop_priority::force_stop) and
|
||||
doing_stuff()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||
@@ -76,7 +84,8 @@ dispatcher::do_stuff() {
|
||||
if (queue.front()->run())
|
||||
queue.pop_front();
|
||||
else {
|
||||
LOG_ERR_("An error occured in one of the tasks, Cameradar will now stop.", "dispatcher");
|
||||
LOG_ERR_("An error occured in one of the tasks, Cameradar will now stop.",
|
||||
"dispatcher");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <fs.h> // fs::home
|
||||
#include <opt_parse.h> // parsing opt
|
||||
#include <dispatcher.h> // program loop
|
||||
#include <iostream> // iostream
|
||||
#include "version.h" // versionning
|
||||
#include <dispatcher.h> // program loop
|
||||
#include <fs.h> // fs::home
|
||||
#include <iostream> // iostream
|
||||
#include <opt_parse.h> // parsing opt
|
||||
|
||||
namespace cmrdr = etix::cameradar;
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <tasks/brutelogs.h>
|
||||
#include <cachemanager.h>
|
||||
#include <tasks/brutelogs.h>
|
||||
|
||||
namespace etix {
|
||||
namespace cameradar {
|
||||
@@ -36,26 +36,26 @@ brutelogs::test_ids(const etix::cameradar::stream_model& stream,
|
||||
std::string path = stream.service_name + "://";
|
||||
if (username != "" || password != "") { path += username + ":" + password + "@"; }
|
||||
path += stream.address + ":" + std::to_string(stream.port);
|
||||
LOG_DEBUG_("Testing ids : " + path, "bruteforce");
|
||||
LOG_DEBUG_("Testing ids : " + path, "brutelogs");
|
||||
try {
|
||||
if (curl_describe(path, true)) {
|
||||
LOG_DEBUG_("[FOUND IDS] : " + path, "bruteforce");
|
||||
LOG_DEBUG_("[FOUND IDS] : " + path, "brutelogs");
|
||||
found = true;
|
||||
stream_model newstream{
|
||||
stream.address, stream.port, username, password,
|
||||
stream.route, stream.service_name, stream.product, stream.protocol,
|
||||
stream.state, stream.path_found, true, stream.thumbnail_path
|
||||
stream.address, stream.port, username, password,
|
||||
stream.route, stream.service_name, stream.product, stream.protocol,
|
||||
stream.state, true, stream.path_found, stream.thumbnail_path
|
||||
};
|
||||
(*cache)->update_stream(newstream);
|
||||
} else {
|
||||
stream_model newstream{ stream.address, stream.port, username,
|
||||
password, stream.route, stream.service_name,
|
||||
stream.product, stream.protocol, stream.state,
|
||||
stream.path_found, false, stream.thumbnail_path };
|
||||
stream_model newstream{ stream.address, stream.port, username,
|
||||
password, stream.route, stream.service_name,
|
||||
stream.product, stream.protocol, stream.state,
|
||||
false, stream.path_found, stream.thumbnail_path };
|
||||
(*cache)->update_stream(newstream);
|
||||
}
|
||||
} catch (const std::runtime_error& e) {
|
||||
LOG_DEBUG_("Ids already tested : " + std::string(e.what()), "bruteforce");
|
||||
LOG_DEBUG_("Ids already tested : " + std::string(e.what()), "brutelogs");
|
||||
}
|
||||
return found;
|
||||
}
|
||||
@@ -75,8 +75,9 @@ brutelogs::run() const {
|
||||
LOG_INFO_(
|
||||
"Beginning bruteforce of the usernames and passwords task, it may "
|
||||
"take a while.",
|
||||
"bruteforce");
|
||||
"brutelogs");
|
||||
std::vector<etix::cameradar::stream_model> streams = (*cache)->get_streams();
|
||||
LOG_DEBUG_("Found " + std::to_string(streams.size()) + " streams in the cache", "brutelogs");
|
||||
bool doubleskip;
|
||||
size_t found = 0;
|
||||
for (const auto& stream : streams) {
|
||||
@@ -88,7 +89,7 @@ brutelogs::run() const {
|
||||
" : This camera's ids were already discovered in "
|
||||
"the database. Skipping to "
|
||||
"the next camera.",
|
||||
"bruteforce");
|
||||
"brutelogs");
|
||||
++found;
|
||||
} else {
|
||||
for (const auto& username : conf.usernames) {
|
||||
@@ -110,12 +111,12 @@ brutelogs::run() const {
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
LOG_WARN_(no_ids_warning_, "bruteforce");
|
||||
LOG_WARN_(no_ids_warning_, "brutelogs");
|
||||
return false;
|
||||
} else
|
||||
LOG_INFO_("Found " + std::to_string(found) + " ids for " + std::to_string(streams.size()) +
|
||||
" cameras",
|
||||
"bruteforce");
|
||||
"brutelogs");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ namespace cameradar {
|
||||
//! problem.
|
||||
bool
|
||||
nmap_is_ok() {
|
||||
return (launch_command("test `dpkg -l | cut -c 5-9 | grep nmap` = nmap")
|
||||
// && launch_command("test `nmap --version | cut -c 14-18 | head -n2 | tail -n1` = 6.47")
|
||||
&& launch_command("mkdir -p scans")); // Creates the directory in which the scans will be stored
|
||||
return (
|
||||
(system("dpkg -l | cut -c 5-9 | grep nmap") == 0)
|
||||
&& launch_command("mkdir -p /tmp/scans")); // Creates the directory in which the scans will be stored
|
||||
}
|
||||
|
||||
//! Launches and checks the return of the nmap command
|
||||
@@ -44,6 +44,7 @@ mapping::run() const {
|
||||
LOG_INFO_("Beginning mapping task. This may take a while.", "mapping");
|
||||
std::string cmd =
|
||||
"nmap -T4 -A " + subnets + " -p " + this->conf.ports + " -oX " + nmap_output;
|
||||
LOG_DEBUG_("Launching nmap : " + cmd, "mapping");
|
||||
bool ret = launch_command(cmd);
|
||||
if (ret)
|
||||
LOG_INFO_("Nmap XML output successfully generated in file: " + nmap_output, "mapping");
|
||||
|
||||
@@ -27,9 +27,9 @@ static const std::string no_hosts_found_ =
|
||||
//! Avoids segfaults on unknown xml structure
|
||||
std::string
|
||||
xml_safe_get(const TiXmlElement* elem, const std::string& attr) {
|
||||
if (elem == nullptr) return "Closed";
|
||||
if (elem == nullptr) return "closed";
|
||||
if (elem->Attribute(attr.c_str()) != nullptr) return std::string(elem->Attribute(attr.c_str()));
|
||||
return "Closed";
|
||||
return "closed";
|
||||
}
|
||||
|
||||
//! Parse a single host node (generally containing only one camera)
|
||||
@@ -51,8 +51,8 @@ parsing::parse_camera(TiXmlElement* xml_host, std::vector<stream_model>& data) c
|
||||
stream.service_name = xml_safe_get(service, "name");
|
||||
stream.product = xml_safe_get(service, "product");
|
||||
} else {
|
||||
stream.service_name = "Closed";
|
||||
stream.product = "Closed";
|
||||
stream.service_name = "closed";
|
||||
stream.product = "closed";
|
||||
}
|
||||
data.push_back(stream);
|
||||
}
|
||||
|
||||
@@ -29,18 +29,25 @@ stream_check::run() const {
|
||||
|
||||
std::vector<stream_model> streams = (*cache)->get_valid_streams();
|
||||
|
||||
if (not streams.size()) {
|
||||
LOG_WARN_("There were no valid streams to check. Cameradar will stop.", "stream_check");
|
||||
return false;
|
||||
}
|
||||
for (const auto& stream : streams) {
|
||||
GError* error = NULL;
|
||||
|
||||
pipeline =
|
||||
gst_parse_launch("rtspsrc name=source ! rtph264depay ! h264parse ! fakesink", &error);
|
||||
|
||||
std::string location = "rtsp://";
|
||||
location += stream.username + ":" + stream.password + "@" + stream.address + ":" + std::to_string(stream.port);
|
||||
if (pipeline == NULL) {
|
||||
LOG_ERR_("[" + stream.address + "] Can't configure pipeline", "stream_check");
|
||||
return false;
|
||||
} else {
|
||||
elem = gst_bin_get_by_name(GST_BIN(pipeline), "source");
|
||||
g_object_set(G_OBJECT(elem), "location", stream.address, "latency", 20, NULL);
|
||||
LOG_DEBUG_("Launching gstreamer check on rtsp://" + stream.username + ":" + stream.password + "@" + stream.address + ":" + std::to_string(stream.port), "gstreamer check");
|
||||
g_object_set(G_OBJECT(elem), "location", location.c_str(), "latency", 20, NULL);
|
||||
|
||||
if (gst_element_set_state(pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
|
||||
LOG_ERR_(
|
||||
|
||||
@@ -48,7 +48,12 @@ bool
|
||||
thumbnail::run() const {
|
||||
std::vector<stream_model> streams = (*cache)->get_valid_streams();
|
||||
LOG_INFO_("Started thumbnail generation, it may take a while", "thumbnail");
|
||||
if (not streams.size()) {
|
||||
LOG_WARN_("There were no valid streams to generate thumbnails from. Cameradar will stop.", "thumbnail_generation");
|
||||
return false;
|
||||
}
|
||||
for (const auto& stream : streams) {
|
||||
LOG_DEBUG_("Generating thumbnail for " + stream.address, "thumbnail_generation");
|
||||
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
|
||||
break;
|
||||
std::string ffmpeg_cmd =
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
## Copyright 2016 Etix Labs
|
||||
##
|
||||
## Licensed under the Apache License, Version 2.0 (the "License");
|
||||
## you may not use this file except in compliance with the License.
|
||||
## You may obtain a copy of the License at
|
||||
##
|
||||
## http://www.apache.org/licenses/LICENSE-2.0
|
||||
##
|
||||
## Unless required by applicable law or agreed to in writing, software
|
||||
## distributed under the License is distributed on an "AS IS" BASIS,
|
||||
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
## See the License for the specific language governing permissions and
|
||||
## limitations under the License.
|
||||
|
||||
message(STATUS "Configuring deps.boost")
|
||||
|
||||
set(BOOST_VERSION 1.60.0)
|
||||
# set(BoostSHA1 2fc96c1651ac6fe9859b678b165bd78dc211e881)
|
||||
|
||||
# Set up general b2 (bjam) command line arguments
|
||||
set(b2Args <SOURCE_DIR>/b2
|
||||
# link=static
|
||||
threading=multi
|
||||
runtime-link=shared
|
||||
--layout=tagged
|
||||
--build-dir=build
|
||||
--without-wave
|
||||
--without-python
|
||||
stage
|
||||
-d+2
|
||||
)
|
||||
|
||||
if(TARGET_ARCH STREQUAL "x86_64")
|
||||
list(APPEND b2Args address-model=64)
|
||||
endif()
|
||||
|
||||
string(REPLACE "." "_" BOOST_VERSION_UNDERSCORE ${BOOST_VERSION})
|
||||
|
||||
set(BOOST_DIR boost)
|
||||
set(BOOST_PATH ${DEPS_DIR}/${BOOST_DIR})
|
||||
|
||||
# Set up build steps
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
deps.boost
|
||||
PREFIX ${BOOST_PATH}
|
||||
URL http://sourceforge.net/projects/boost/files/boost/${BOOST_VERSION}/boost_${BOOST_VERSION_UNDERSCORE}.tar.bz2/download
|
||||
TIMEOUT 600
|
||||
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E make_directory <SOURCE_DIR>/build
|
||||
BUILD_COMMAND "${b2Args}"
|
||||
# BUILD_COMMAND "<SOURCE_DIR>/b2 address-model=64 threading=multi runtime-link=shared --layout=tagged --build-dir=<SOURCE_DIR>/build"
|
||||
BUILD_IN_SOURCE ON
|
||||
INSTALL_COMMAND ""
|
||||
# INSTALL_COMMAND <SOURCE_DIR>/b2 install --prefix=${BOOST_PATH}
|
||||
LOG_DOWNLOAD ON
|
||||
LOG_UPDATE ON
|
||||
LOG_CONFIGURE ON
|
||||
LOG_BUILD ON
|
||||
LOG_TEST ON
|
||||
LOG_INSTALL ON
|
||||
)
|
||||
|
||||
# Set extra step to build b2 (bjam)
|
||||
set(b2Bootstrap "./bootstrap.sh")
|
||||
ExternalProject_Add_Step(
|
||||
deps.boost
|
||||
make_b2
|
||||
COMMAND ${b2Bootstrap}
|
||||
COMMENT "Building b2..."
|
||||
DEPENDEES download
|
||||
DEPENDERS configure
|
||||
WORKING_DIRECTORY <SOURCE_DIR>
|
||||
LOG ON
|
||||
)
|
||||
|
||||
|
||||
ExternalProject_Get_Property(deps.boost SOURCE_DIR)
|
||||
set(BOOST_INCLUDE_DIR ${SOURCE_DIR} PARENT_SCOPE)
|
||||
set(BOOST_LIBRARY_DIR "${SOURCE_DIR}/stage/lib")
|
||||
set(BOOST_LIBRARY_DIR ${BOOST_LIBRARY_DIR} PARENT_SCOPE)
|
||||
|
||||
# list all the boost libraries .dylib/.so
|
||||
file(GLOB BOOST_INSTALL_DEPENDENCIES "${BOOST_LIBRARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}boost_*${CMAKE_SHARED_LIBRARY_SUFFIX}")
|
||||
list (APPEND CAMERADAR_INSTALL_DEPENDENCIES ${BOOST_INSTALL_DEPENDENCIES})
|
||||
# on linux
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
file(GLOB BOOST_INSTALL_DEPENDENCIES "${BOOST_LIBRARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}boost_*${CMAKE_SHARED_LIBRARY_SUFFIX}.${BOOST_VERSION}")
|
||||
list (APPEND CAMERADAR_INSTALL_DEPENDENCIES ${BOOST_INSTALL_DEPENDENCIES})
|
||||
endif()
|
||||
|
||||
set(CAMERADAR_INSTALL_DEPENDENCIES ${CAMERADAR_INSTALL_DEPENDENCIES} PARENT_SCOPE)
|
||||
@@ -0,0 +1,43 @@
|
||||
# Copyright (C) 2015 Etix Labs - All Rights Reserved.
|
||||
# All information contained herein is, and remains the property of Etix Labs and its suppliers,
|
||||
# if any. The intellectual and technical concepts contained herein are proprietary to Etix Labs
|
||||
# Dissemination of this information or reproduction of this material is strictly forbidden unless
|
||||
# prior written permission is obtained from Etix Labs.
|
||||
|
||||
# MySQL Connector dependency
|
||||
message(STATUS "Configuring deps.mysqlconnector")
|
||||
|
||||
set (MYSQL_CONNECTOR_VERSION 1.1.6)
|
||||
set (MD5 9e49dcfc1408b18b3d3ca02781ff7efb)
|
||||
set (MYSQL_CONNECTOR_DIR mysql-connector)
|
||||
set (MYSQL_CONNECTOR_PATH ${DEPS_DIR}/${MYSQL_CONNECTOR_DIR})
|
||||
|
||||
set (BOOST_ROOT_DIR ${DEPS_DIR}/boost/src/deps.boost)
|
||||
|
||||
# include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
deps.mysql_connector
|
||||
PREFIX ${MYSQL_CONNECTOR_PATH}
|
||||
URL http://dev.mysql.com/get/Downloads/Connector-C++/mysql-connector-c++-${MYSQL_CONNECTOR_VERSION}.tar.gz
|
||||
URL_HASH MD5=${MD5}
|
||||
CONFIGURE_COMMAND ${CMAKE_COMMAND} -DBOOST_ROOT=${BOOST_ROOT_DIR} "-DCMAKE_INSTALL_PREFIX=${MYSQL_CONNECTOR_PATH}" -DBUILD_TYPE=Release -DMYSQL_CXXFLAGS=-fexceptions <SOURCE_DIR>
|
||||
BUILD_IN_SOURCE ON
|
||||
UPDATE_COMMAND ""
|
||||
BUILD_COMMAND ${CMAKE_MAKE_PROGRAM}
|
||||
INSTALL_COMMAND ${CMAKE_MAKE_PROGRAM} install
|
||||
LOG_DOWNLOAD ON
|
||||
LOG_UPDATE ON
|
||||
LOG_CONFIGURE ON
|
||||
LOG_BUILD ON
|
||||
)
|
||||
|
||||
set (MYSQL_CONNECTOR_INCLUDE_DIR "${MYSQL_CONNECTOR_PATH}/include" PARENT_SCOPE)
|
||||
set (MYSQL_CONNECTOR_LIBRARY_DIR "${MYSQL_CONNECTOR_PATH}/lib")
|
||||
|
||||
set (MYSQL_CONNECTOR_LIBRARY_DIR ${MYSQL_CONNECTOR_LIBRARY_DIR} PARENT_SCOPE)
|
||||
|
||||
# list all the hiredis libraries
|
||||
file(GLOB MYSQL_CONNECTOR_INSTALL_DEPENDENCIES "${MYSQL_CONNECTOR_LIBRARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}mysqlcppconn*${CMAKE_SHARED_LIBRARY_SUFFIX}*")
|
||||
list (APPEND CAMERADAR_INSTALL_DEPENDENCIES ${MYSQL_CONNECTOR_INSTALL_DEPENDENCIES})
|
||||
|
||||
set(CAMERADAR_INSTALL_DEPENDENCIES ${CAMERADAR_INSTALL_DEPENDENCIES} PARENT_SCOPE)
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:15.10
|
||||
FROM ubuntu:16.04
|
||||
|
||||
MAINTAINER brendan.leglaunec@etixgroup.com
|
||||
|
||||
@@ -7,10 +7,13 @@ ENV LD_LIBRARY_PATH="/cameradar/libraries"
|
||||
RUN apt-get update && apt-get install -y \
|
||||
nmap \
|
||||
ffmpeg \
|
||||
libboost-all-dev \
|
||||
libgstreamer1.0-dev \
|
||||
gstreamer1.0-plugins-base \
|
||||
gstreamer1.0-plugins-good \
|
||||
libcurl4-openssl-dev
|
||||
libcurl4-openssl-dev \
|
||||
libmysqlclient20 \
|
||||
mysql-client
|
||||
|
||||
ADD cameradar_*_Release_Linux.tar.gz /
|
||||
RUN mv cameradar_*_Release_Linux cameradar
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,13 @@
|
||||
{
|
||||
"subnets" : "172.16.100.13,localhost",
|
||||
"mysql_db" : {
|
||||
"host" : "__MYSQL_ADDR__",
|
||||
"port" : __MYSQL_PORT__,
|
||||
"user": "root",
|
||||
"password": "root",
|
||||
"db_name": "cmrdr"
|
||||
},
|
||||
|
||||
"subnets" : "localhost",
|
||||
|
||||
// If not specified, will scan all ports (1-65535)
|
||||
"ports" : "554,8554",
|
||||
@@ -9,6 +17,9 @@
|
||||
// You must give an accessible path to an already existing directory
|
||||
"thumbnail_storage_path" : "/tmp",
|
||||
|
||||
// This is the path that will be used in the Docker container
|
||||
// if you're not familiar with Docker, only change the
|
||||
// cache_manager_name value
|
||||
"cache_manager_path" : "/cameradar/cache_managers",
|
||||
"cache_manager_name" : "dumb"
|
||||
"cache_manager_name" : "mysql"
|
||||
}
|
||||
|
||||
@@ -5,3 +5,12 @@ cameradar:
|
||||
volumes:
|
||||
- "./conf:/tmp/conf:ro"
|
||||
- "./cameradar_thumbnails:/tmp/cameradar_thumbnails"
|
||||
links:
|
||||
- mysql
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: cmrdr
|
||||
ports:
|
||||
- "3306:3306"
|
||||
|
||||
@@ -23,6 +23,22 @@ echo -n "replacing cameras ports in configuration "
|
||||
sed -i s#__PORTS_TO_CHECK__#$CAMERAS_PORTS#g $CONF
|
||||
echo -e $COL_GREEN"ok"$COL_RESET
|
||||
|
||||
# Replace ext_cctv_mysql with the IP address of your DB or the name of its Docker
|
||||
# container. The container has to be linked in docker-compose.yml for cameradar
|
||||
# to be able to interact with it.
|
||||
echo -n "replacing mysql host and port in configuration "
|
||||
sed -i s#__MYSQL_ADDR__#mysql#g $CONF
|
||||
|
||||
# Reaplce 3306 with the port of your DB
|
||||
sed -i s#__MYSQL_PORT__#3306#g $CONF
|
||||
echo -e $COL_GREEN"ok"$COL_RESET
|
||||
|
||||
echo -n "waiting for mysql to be ready "
|
||||
while ! mysqladmin ping -h"mysql" -P3306 --silent; do
|
||||
sleep 1
|
||||
done
|
||||
echo -e $COL_GREEN"ok"$COL_RESET
|
||||
|
||||
/cameradar/bin/cameradar -l 1 -c /conf/cameradar.conf.json &
|
||||
cameradar_pid=$!
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -17,7 +17,7 @@ cmake_minimum_required (VERSION 2.8.1)
|
||||
# Lib subdirectory
|
||||
|
||||
include (jsoncpp)
|
||||
include (boost)
|
||||
include (mysql_connector)
|
||||
|
||||
set_directory_properties(PROPERTIES CLEAN_NO_CUSTOM ON)
|
||||
set (CAMERADAR_INSTALL_DEPENDENCIES ${CAMERADAR_INSTALL_DEPENDENCIES} PARENT_SCOPE)
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
FROM ubuntu:15.10
|
||||
|
||||
MAINTAINER brendan.leglaunec@etixgroup.com
|
||||
|
||||
ENV LD_LIBRARY_PATH="/cctv/libraries"
|
||||
|
||||
# install go
|
||||
RUN apt-get update && apt-get install -y make git wget curl
|
||||
RUN wget https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz
|
||||
RUN tar -C /usr/local -xzf go1.6.linux-amd64.tar.gz
|
||||
# set variable env
|
||||
ENV GOPATH=/go
|
||||
ENV PATH=$PATH:/go/bin
|
||||
ENV PATH=$PATH:/usr/local/go/bin
|
||||
ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
|
||||
|
||||
# needed for cameradar
|
||||
RUN apt-get update && apt-get install -y \
|
||||
nmap \
|
||||
libmysqlclient18 \
|
||||
ffmpeg \
|
||||
mysql-client \
|
||||
libgstreamer1.0-dev \
|
||||
gstreamer1.0-plugins-base \
|
||||
gstreamer1.0-plugins-good \
|
||||
libcurl4-openssl-dev
|
||||
|
||||
RUN apt-get install -y psmisc
|
||||
|
||||
ADD cctv_*_Debug_Linux.tar.gz /
|
||||
RUN mv cctv_*_Debug_Linux cctv
|
||||
|
||||
# create cameradaratest folder in go src path
|
||||
RUN mkdir -p /go/src/cameradartest
|
||||
ADD ./conf /conf
|
||||
ADD ./docker/run_cameradartest.sh /run.sh
|
||||
|
||||
# get go deps
|
||||
RUN go get github.com/go-sql-driver/mysql
|
||||
|
||||
RUN mkdir /thumbnails
|
||||
WORKDIR /go/src/cameradartest
|
||||
CMD ["/run.sh"]
|
||||
@@ -0,0 +1,14 @@
|
||||
FROM ubuntu:15.10
|
||||
MAINTAINER brendan.leglaunec@etixgroup.com
|
||||
|
||||
RUN useradd -m vlc; \
|
||||
apt-get update; \
|
||||
apt-get install -y vlc-nox
|
||||
|
||||
RUN sed -i s/geteuid/getppid/g /usr/bin/vlc
|
||||
|
||||
ADD ./docker/screen.png /vlc/screen.png
|
||||
COPY ./docker/run_vlc.sh /start.sh
|
||||
COPY ./etix_rtsp_server /etix_rtsp_server
|
||||
|
||||
EXPOSE 8554
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"mysql_db" : {
|
||||
"host" : "0.0.0.0",
|
||||
"port" : 3306,
|
||||
"user": "root",
|
||||
"password": "root",
|
||||
"db_name": "cctv"
|
||||
},
|
||||
"subnets" : "172.16.100.13 localhost",
|
||||
"ports" : "554,8554", // if not specified, default will be 1-65535
|
||||
"rtsp_url_file" : "conf/url.json",
|
||||
"rtsp_ids_file" : "conf/ids.json",
|
||||
"thumbnail_storage_path" : "/ce/que/tu/veux"
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"Output": "cameratest.log.xml",
|
||||
|
||||
"Cameradar" : {
|
||||
"Path": "/home/ullaakut/Work/cctv_server2/cameradar/test/cameradar",
|
||||
"Args": "-l 1 -c tmp_config",
|
||||
"Ports": "554,5554,8554",
|
||||
"IdsPath": "conf/ids.json",
|
||||
"RoutesPath": "conf/url.json",
|
||||
"ThumbPath": "/home/ullaakut/.cctv",
|
||||
"dbHost": "0.0.0.0",
|
||||
"dbPort": 3306,
|
||||
"dbUser": "root",
|
||||
"dbPassword": "root",
|
||||
"dbName": "cctv",
|
||||
"Console": false
|
||||
},
|
||||
"Tests" : [
|
||||
{
|
||||
"address" : "127.0.0.1",
|
||||
"password" : "",
|
||||
"port" : "8554",
|
||||
"route" : "live.sdp",
|
||||
"username" : "",
|
||||
"valid" : true
|
||||
},
|
||||
{
|
||||
"address" : "172.16.100.11",
|
||||
"password" : "",
|
||||
"port" : "553",
|
||||
"route" : "live.sdp",
|
||||
"username" : "admin",
|
||||
"valid" : false
|
||||
},
|
||||
{
|
||||
"address" : "172.16.100.13",
|
||||
"password" : "",
|
||||
"port" : "554",
|
||||
"route" : "live.sdp",
|
||||
"username" : "admin",
|
||||
"valid" : true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"username": [
|
||||
"",
|
||||
"admin",
|
||||
"Admin",
|
||||
"root",
|
||||
"supervisor",
|
||||
"ubnt"
|
||||
],
|
||||
"password" : [
|
||||
"",
|
||||
"admin",
|
||||
"9999",
|
||||
"123456",
|
||||
"pass",
|
||||
"camera",
|
||||
"1234",
|
||||
"12345",
|
||||
"fliradmin",
|
||||
"system",
|
||||
"jvc",
|
||||
"meinsm",
|
||||
"root",
|
||||
"4321",
|
||||
"1111111",
|
||||
"password",
|
||||
"ikwd",
|
||||
"supervisor",
|
||||
"ubnt"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"urls" : [
|
||||
"/",
|
||||
"/1.AMP",
|
||||
"/1/stream1",
|
||||
"/CAM_ID.password.mp2",
|
||||
"/GetData.cgi",
|
||||
"/MediaInput/h264",
|
||||
"/MediaInput/mpeg4",
|
||||
"/VideoInput/1/h264/1",
|
||||
"/access_code",
|
||||
"/access_name_for_stream_1_to_5",
|
||||
"/av0_0",
|
||||
"/av2",
|
||||
"/avn=2",
|
||||
"/axis-media/media.amp",
|
||||
"/cam",
|
||||
"/cam0_0",
|
||||
"/cam0_1",
|
||||
"/cam1/h264",
|
||||
"/cam1/h264/multicast",
|
||||
"/cam1/mjpeg",
|
||||
"/cam1/mpeg4",
|
||||
"/camera.stm",
|
||||
"/ch0",
|
||||
"/ch001.sdp",
|
||||
"/ch0_unicast_firststream",
|
||||
"/ch0_unicast_secondstream",
|
||||
"/channel1",
|
||||
"/h264",
|
||||
"/h264/media.amp",
|
||||
"/image.mpg",
|
||||
"/img/media.sav",
|
||||
"/img/video.asf",
|
||||
"/img/video.sav",
|
||||
"/ioImage/1",
|
||||
"/ipcam.sdp",
|
||||
"/ipcam_h264.sdp",
|
||||
"/live.sdp",
|
||||
"/live/h264",
|
||||
"/live/mpeg4",
|
||||
"/live_mpeg4.sdp",
|
||||
"/livestream",
|
||||
"/livestream/",
|
||||
"/media/media.amp",
|
||||
"/media/video1",
|
||||
"/mjpeg/media.smp",
|
||||
"/mp4",
|
||||
"/mpeg4",
|
||||
"/mpeg4/1/media.amp",
|
||||
"/mpeg4/media.amp",
|
||||
"/mpeg4/media.smp",
|
||||
"/mpeg4unicast",
|
||||
"/mpg4/rtsp.amp",
|
||||
"/multicaststream",
|
||||
"/now.mp4",
|
||||
"/nph-h264.cgi",
|
||||
"/nphMpeg4/g726-640x",
|
||||
"/nphMpeg4/g726-640x480",
|
||||
"/nphMpeg4/nil-320x240",
|
||||
"/play1.sdp",
|
||||
"/play2.sdp",
|
||||
"/rtpvideo1.sdp",
|
||||
"/rtsp_tunnel",
|
||||
"/rtsph264",
|
||||
"/stream1",
|
||||
"/user.pin.mp2",
|
||||
"/user_defined",
|
||||
"/video",
|
||||
"/video.3gp",
|
||||
"/video.mp4",
|
||||
"/video1",
|
||||
"/video1+audio1",
|
||||
"/vis",
|
||||
"/wfov"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"Output": "cameratest.log.xml",
|
||||
"Cameradar" : {
|
||||
"Path": "/cctv/bin/cameradar",
|
||||
"Args": "-l 1 -c tmp_config",
|
||||
"Ports": "554,5554,8554,5548",
|
||||
"IdsPath": "/conf/ids.json",
|
||||
"RoutesPath": "/conf/url.json",
|
||||
"ThumbPath": "/thumbnails",
|
||||
"dbHost": "mysql_cameradar",
|
||||
"dbPort": 3306,
|
||||
"dbUser": "root",
|
||||
"dbPassword": "root",
|
||||
"dbName": "cctv",
|
||||
"Console": false
|
||||
},
|
||||
"Tests" : __CAMERAS__
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"username": [
|
||||
"",
|
||||
"admin",
|
||||
"Admin",
|
||||
"root",
|
||||
"supervisor",
|
||||
"ubnt"
|
||||
],
|
||||
"password" : [
|
||||
"",
|
||||
"admin",
|
||||
"9999",
|
||||
"123456",
|
||||
"pass",
|
||||
"camera",
|
||||
"1234",
|
||||
"12345",
|
||||
"fliradmin",
|
||||
"system",
|
||||
"jvc",
|
||||
"meinsm",
|
||||
"root",
|
||||
"4321",
|
||||
"1111111",
|
||||
"password",
|
||||
"ikwd",
|
||||
"supervisor",
|
||||
"ubnt"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"urls" : [
|
||||
"/",
|
||||
"/1.AMP",
|
||||
"/1/stream1",
|
||||
"/CAM_ID.password.mp2",
|
||||
"/GetData.cgi",
|
||||
"/MediaInput/h264",
|
||||
"/MediaInput/mpeg4",
|
||||
"/VideoInput/1/h264/1",
|
||||
"/access_code",
|
||||
"/access_name_for_stream_1_to_5",
|
||||
"/av0_0",
|
||||
"/av2",
|
||||
"/avn=2",
|
||||
"/axis-media/media.amp",
|
||||
"/cam",
|
||||
"/cam0_0",
|
||||
"/cam0_1",
|
||||
"/cam1/h264",
|
||||
"/cam1/h264/multicast",
|
||||
"/cam1/mjpeg",
|
||||
"/cam1/mpeg4",
|
||||
"/camera.stm",
|
||||
"/ch0",
|
||||
"/ch001.sdp",
|
||||
"/ch0_unicast_firststream",
|
||||
"/ch0_unicast_secondstream",
|
||||
"/channel1",
|
||||
"/h264",
|
||||
"/h264/media.amp",
|
||||
"/image.mpg",
|
||||
"/img/media.sav",
|
||||
"/img/video.asf",
|
||||
"/img/video.sav",
|
||||
"/ioImage/1",
|
||||
"/ipcam.sdp",
|
||||
"/ipcam_h264.sdp",
|
||||
"/live.sdp",
|
||||
"/live/h264",
|
||||
"/live/mpeg4",
|
||||
"/live_mpeg4.sdp",
|
||||
"/livestream",
|
||||
"/livestream/",
|
||||
"/media/media.amp",
|
||||
"/media/video1",
|
||||
"/mjpeg/media.smp",
|
||||
"/mp4",
|
||||
"/mpeg4",
|
||||
"/mpeg4/1/media.amp",
|
||||
"/mpeg4/media.amp",
|
||||
"/mpeg4/media.smp",
|
||||
"/mpeg4unicast",
|
||||
"/mpg4/rtsp.amp",
|
||||
"/multicaststream",
|
||||
"/now.mp4",
|
||||
"/nph-h264.cgi",
|
||||
"/nphMpeg4/g726-640x",
|
||||
"/nphMpeg4/g726-640x480",
|
||||
"/nphMpeg4/nil-320x240",
|
||||
"/play1.sdp",
|
||||
"/play2.sdp",
|
||||
"/rtpvideo1.sdp",
|
||||
"/rtsp_tunnel",
|
||||
"/rtsph264",
|
||||
"/stream1",
|
||||
"/user.pin.mp2",
|
||||
"/user_defined",
|
||||
"/video",
|
||||
"/video.3gp",
|
||||
"/video.mp4",
|
||||
"/video1",
|
||||
"/video1+audio1",
|
||||
"/vis",
|
||||
"/wfov"
|
||||
]
|
||||
}
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/bin/bash
|
||||
|
||||
ports=('8554' '8554' '8554' '8554' '8554' '8554')
|
||||
users=('admin' 'root' 'ubnt' 'Admin' 'supervisor' '')
|
||||
passwords=('admin' 'root' '12345' 'ubnt' 'password' '')
|
||||
routes=('live.sdp' 'live.sdp' 'ch001.sdp' '' 'invalid' 'live_mpeg4.sdp')
|
||||
cams_name_pattern="fake_camera_"
|
||||
|
||||
# json generation variable only
|
||||
json="[\n"
|
||||
first=true
|
||||
# $1 = adress, $2 = port, $3 = path, $4 = usernam $5 = password, $6 = valid
|
||||
function make_json {
|
||||
if [ "$first" = true ] ; then first=false
|
||||
else json="$json,\n"; fi
|
||||
json="$json{"
|
||||
json="$json\"address\":\"$1\","
|
||||
json="$json\"port\":\"$2\","
|
||||
json="$json\"route\":\"$3\","
|
||||
json="$json\"username\":\"$4\","
|
||||
json="$json\"password\":\"$5\","
|
||||
json="$json\"valid\":$6"
|
||||
json="$json}"
|
||||
}
|
||||
|
||||
# $1 = configuration template path
|
||||
function generate_conf {
|
||||
echo "generate configuration"
|
||||
sed s#__CAMERAS__#$json#g $1 > cameradartest.conf.json
|
||||
}
|
||||
|
||||
# $1 = numbers of cameras to generate
|
||||
function start {
|
||||
# Seed random generator
|
||||
RANDOM=$(date +%s)
|
||||
|
||||
# start cameras
|
||||
for (( i=1; i<=$1; i++ )); do
|
||||
name="$cams_name_pattern$i"
|
||||
# random conf
|
||||
conf_idx=$(($RANDOM % ${#ports[@]}))
|
||||
|
||||
# get conf variables
|
||||
port=${ports[$conf_idx]}
|
||||
user=${users[$conf_idx]}
|
||||
passw=${passwords[$conf_idx]}
|
||||
route=${routes[$conf_idx]}
|
||||
is_valid=true
|
||||
|
||||
# if conf_idx = 4 -> invalid conf
|
||||
if [ "$conf_idx" == "4" ] ; then is_valid=false; fi
|
||||
|
||||
docker run -d --name "$name" fake-camera /start.sh "$port" "$user" "$passw" "$route"
|
||||
make_json "$name" "$port" "$route" "$user" "$passw" $is_valid
|
||||
done
|
||||
|
||||
# finalize json
|
||||
json="$json]"
|
||||
echo "$json"
|
||||
}
|
||||
|
||||
function stop {
|
||||
# if no cameras containers are started just exit
|
||||
camera_count="`docker ps -a -q --filter="name=$cams_name_pattern" | wc -l`"
|
||||
if [ "$camera_count" == "0" ]; then
|
||||
echo "error: no cameras started"; exit 1
|
||||
fi
|
||||
|
||||
echo "stopping and removing $camera_count containers"
|
||||
# docker stop $(docker ps -a -q --filter="name=$cams_name_pattern")
|
||||
docker rm -f $(docker ps -a -q --filter="name=$cams_name_pattern") > /dev/null
|
||||
}
|
||||
|
||||
# need first argument at least
|
||||
if [ "$1" == "" ]; then
|
||||
echo "error: invalid number of argument"
|
||||
exit 1
|
||||
fi
|
||||
case $1 in
|
||||
"start")
|
||||
# check if the argument is a number.
|
||||
re='^[0-9]+$'
|
||||
if ! [[ $2 =~ $re ]] ; then
|
||||
echo "error: argument is not a number"; exit 1
|
||||
fi
|
||||
if [[ "$3" == "" ]] ; then
|
||||
echo "error: missing path to the configuration file template"; exit 1
|
||||
fi
|
||||
echo "starting $2 cameras"
|
||||
start $2
|
||||
generate_conf $3
|
||||
;;
|
||||
"stop")
|
||||
echo "stopping all cameras tests"
|
||||
stop
|
||||
;;
|
||||
"help")
|
||||
echo "./gen_cameras.sh start CAMS_NB - start CAMS_NB cameras"
|
||||
echo " stop - stop all started cameras"
|
||||
echo " help - display this help"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "invalid test name"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
while ! mysqladmin ping -h"mysql_cameradar" -P3306 --silent; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
ls -alhR /conf
|
||||
cat /etc/hosts
|
||||
|
||||
# build
|
||||
go build
|
||||
# run test
|
||||
./cameradartest /tmp/tests/cameradartest.conf.json
|
||||
|
||||
cp cameratest.log.xml /tmp/tests/
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
port=$1
|
||||
user=$2
|
||||
passw=$3
|
||||
route=$4
|
||||
url=""
|
||||
|
||||
# need first argument at least
|
||||
if [ "$2" == "" ]; then
|
||||
url="rtsp://:$port/$route"
|
||||
else
|
||||
url="rtsp://$user:$passw@:$port/$route"
|
||||
fi
|
||||
./etix_rtsp_server -u $s -p $3 -r $4
|
||||
# cvlc /vlc/screen.png -I dummy --sout-keep --no-drop-late-frames --no-skip-frames --image-duration 9999 --sout="#transcode{vcodec=h264,fps=15,venc=x264{preset=ultrafast,tune=zerolatency,keyint=30,bframes=0,ref=1,level=30,profile=baseline,hrd=cbr,crf=20,ratetol=1.0,vbv-maxrate=1200,vbv-bufsize=1200,lookahead=0}}:rtp{sdp=$url}" --sout-all
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 558 KiB |
Executable
BIN
Binary file not shown.
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
|
||||
# check if a debug package exist in the current folder
|
||||
if ! ls ./cctv_*_Debug_Linux.tar.gz 1> /dev/null 2>&1; then
|
||||
(echo "no debug package in the current folder"; exit 137)
|
||||
exit 137
|
||||
fi
|
||||
|
||||
cams_name_pattern="fake_camera_"
|
||||
cmd=""
|
||||
|
||||
function make_docker_command {
|
||||
cmd="docker run --rm"
|
||||
|
||||
# start cameras
|
||||
for (( i=1; i<=$1; i++ )); do
|
||||
name="$cams_name_pattern$i"
|
||||
cmd="$cmd --link=\"$name\""
|
||||
done
|
||||
|
||||
# add mysql libk
|
||||
cmd="$cmd --link=\"mysql_cameradar\""
|
||||
# add cameradar srcs
|
||||
cmd="$cmd -v \"`pwd`/src:/go/src/cameradartest\""
|
||||
# add cmaeradar conf
|
||||
cmd="$cmd -v \"`pwd`/:/tmp/tests\""
|
||||
# add container name
|
||||
cmd="$cmd cameradartest"
|
||||
}
|
||||
|
||||
function start_test {
|
||||
make_docker_command $1
|
||||
./docker/gen_cameras.sh start $1 ./docker/cameratest.conf.tmpl.json
|
||||
eval $cmd
|
||||
./docker/gen_cameras.sh stop
|
||||
}
|
||||
|
||||
# build images
|
||||
echo "building docker images"
|
||||
# building fake-camera container
|
||||
docker build -f Dockerfile-camera -t fake-camera .
|
||||
|
||||
# building cameradartest image
|
||||
docker build -t cameradartest .
|
||||
|
||||
# getting mysql
|
||||
echo "starting mysql"
|
||||
docker pull mysql:5.7
|
||||
docker run --name mysql_cameradar -e MYSQL_DATABASE=cctv -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7
|
||||
|
||||
start_test 1
|
||||
start_test 5
|
||||
# start_test 10
|
||||
# start_test 20
|
||||
|
||||
# stop mysql
|
||||
echo "stopping mysql"
|
||||
docker rm -f mysql_cameradar
|
||||
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func (m *manager) parseConfig() bool {
|
||||
// Get config file path
|
||||
confPath := "conf/cameratest.conf.json"
|
||||
av := len(os.Args)
|
||||
if av == 2 {
|
||||
confPath = os.Args[1]
|
||||
}
|
||||
|
||||
// Load config
|
||||
fmt.Printf("Loading config file: %s ... ", confPath)
|
||||
configFile, err := os.Open(confPath)
|
||||
if err != nil {
|
||||
fmt.Printf("\nCan't open config file: %s\n", err)
|
||||
return false
|
||||
}
|
||||
dec := json.NewDecoder(configFile)
|
||||
if err = dec.Decode(&m); err != nil {
|
||||
fmt.Printf("\nUnable to deserialize config file: %s\n", err)
|
||||
return false
|
||||
}
|
||||
fmt.Println("Configuration file successfully loaded\n")
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (m *manager) dropDB() bool {
|
||||
dsn := m.DB.User + ":" + m.DB.Password + "@" + "tcp(" + m.DB.Host + ":" + strconv.Itoa(m.DB.Port) + ")/" + m.DB.Db_name + "?charset=utf8"
|
||||
db, err := sql.Open("mysql", dsn)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
defer db.Close()
|
||||
q := "DROP DATABASE cctv;"
|
||||
_, err = db.Exec(q)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println("------ Dropped CCTV Database -------")
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Launch it via goroutine
|
||||
// Start read log of service
|
||||
func getResult(test *[]Result, resultPath string) bool {
|
||||
// Load config
|
||||
resultFile, err := os.Open(resultPath)
|
||||
if err != nil {
|
||||
fmt.Printf("\nCan't open result file: %s\n", err)
|
||||
return false
|
||||
}
|
||||
dec := json.NewDecoder(resultFile)
|
||||
if err = dec.Decode(&test); err != nil {
|
||||
fmt.Printf("\nUnable to deserialize result file: %s\n", err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Launch it via goroutine
|
||||
// Start read log of service
|
||||
func readLog(service *Service, reader io.ReadCloser) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
for scanner.Scan() {
|
||||
str := scanner.Text()
|
||||
if service.Console {
|
||||
fmt.Printf("[%s] %s\n", service.Path, str)
|
||||
}
|
||||
fmt.Printf("%s\n", str)
|
||||
service.Mutex.Lock()
|
||||
service.Logs = append(service.Logs, str)
|
||||
service.Mutex.Unlock()
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
fmt.Printf("[%s] Service failed: %s\n", service.Path, err)
|
||||
}
|
||||
fmt.Printf("Logger of service: [%s] stopped\n", service.Path)
|
||||
service.Active = false
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
manager := new(manager)
|
||||
defer manager.Stop()
|
||||
|
||||
// Parse conf (streams should already be launched by Jenkins)
|
||||
fmt.Println("--- Initializing Cameradar Test Tool ... ---")
|
||||
if !manager.Init() {
|
||||
fmt.Println("-> Cameradar Test Tool initialization FAILED")
|
||||
return
|
||||
}
|
||||
|
||||
// Run tests
|
||||
if !manager.Run() {
|
||||
fmt.Println("-> Cameradar Test Tool FAILED")
|
||||
}
|
||||
|
||||
// Write results
|
||||
fmt.Println("--- Writing results... ---")
|
||||
if !manager.WriteResults(*(manager.Result), manager.Config.Output) {
|
||||
fmt.Println("-> Write results FAILED")
|
||||
return
|
||||
}
|
||||
fmt.Println("--- Writing results done ---")
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type manager struct {
|
||||
Config
|
||||
|
||||
Tests []Result
|
||||
Result *TestCase
|
||||
DB mysql_db
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Cameradar Service `json:"Cameradar"`
|
||||
|
||||
Output string
|
||||
}
|
||||
|
||||
func (m *manager) Init() bool {
|
||||
fmt.Println("- Parsing")
|
||||
if !m.parseConfig() {
|
||||
return false
|
||||
}
|
||||
|
||||
fmt.Println("- Cleaning content")
|
||||
killService(&m.Config.Cameradar)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *manager) Run() bool {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
fmt.Println("\n- Launching all tests")
|
||||
var newTest = new(TestCase)
|
||||
newTest.expected = m.Tests
|
||||
if (m.generateConfig(m.Tests, &m.DB)) {
|
||||
m.dropDB()
|
||||
wg.Add(1)
|
||||
go m.invokeTestCase(newTest, &wg)
|
||||
m.Result = newTest
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("All tests completed\n")
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *manager) Stop() bool {
|
||||
killService(&m.Config.Cameradar)
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
Path string `json:"Path"`
|
||||
Args string `json:"Args"`
|
||||
Ports string `json:"Ports"`
|
||||
IdsPath string `json:"IdsPath"`
|
||||
RoutesPath string `json:"RoutesPath"`
|
||||
DbHost string `json:"dbHost"`
|
||||
DbPort int `json:"dbPort"`
|
||||
DbUser string `json:"dbUser"`
|
||||
DbPassword string `json:"dbPassword"`
|
||||
DbName string `json:"dbName"`
|
||||
ThumbPath string `json:"ThumbPath"`
|
||||
Console bool `json:"Console"`
|
||||
|
||||
Logs []string
|
||||
Active bool // Based on io.ReadCloser status
|
||||
|
||||
Mutex sync.Mutex
|
||||
cmd *exec.Cmd // Go handler of the service
|
||||
}
|
||||
|
||||
func startService(service *Service) bool {
|
||||
// Launch service
|
||||
args := strings.Fields(service.Args)
|
||||
service.cmd = exec.Command(service.Path, args...)
|
||||
|
||||
handler, err := service.cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return false
|
||||
}
|
||||
errHandler, err := service.cmd.StderrPipe()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return false
|
||||
}
|
||||
// Launch
|
||||
err = service.cmd.Start()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return false
|
||||
}
|
||||
|
||||
fmt.Printf("Service: [%s] started\n", service.Path)
|
||||
service.Active = true
|
||||
|
||||
// Read service logs and update service status
|
||||
// Set pipes
|
||||
go readLog(service, handler)
|
||||
go readLog(service, errHandler)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Stop only specified service instance
|
||||
func stopService(service *Service) {
|
||||
service.cmd.Process.Kill()
|
||||
}
|
||||
|
||||
// Kill all instances of specified service
|
||||
func killService(service *Service) {
|
||||
// Sending SIGTERM
|
||||
fmt.Printf("Executing: killall %s\n", service.Path)
|
||||
cmd := exec.Command("killall", service.Path)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
// Sending SIGABORT, more reliable for VLC
|
||||
sigAbort := []string{service.Path, "-s", "SIGABRT"}
|
||||
fmt.Printf("Executing: killall %s -s SIGABRT\n", service.Path)
|
||||
cmd = exec.Command("killall", sigAbort...)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
"net"
|
||||
)
|
||||
|
||||
type mysql_db struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
Db_name string `json:"db_name"`
|
||||
}
|
||||
|
||||
type CameradarConfig struct {
|
||||
Mysql_db mysql_db `json:"mysql_db"`
|
||||
Subnets string `json:"subnets"`
|
||||
Ports string `json:"ports"`
|
||||
Rtsp_url_file string `json:"rtsp_url_file"`
|
||||
Rtsp_ids_file string `json:"rtsp_ids_file"`
|
||||
Thumbnail_storage_path string `json:"thumbnail_storage_path"`
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Address string `json:"address"`
|
||||
Password string `json:"password"`
|
||||
Port string `json:"port"`
|
||||
Route string `json:"route"`
|
||||
Username string `json:"username"`
|
||||
Valid bool `json:"valid,omitempty"`
|
||||
Thumb string `json:"thumbnail_path,omitempty"`
|
||||
}
|
||||
|
||||
type TestCase struct {
|
||||
expected []Result
|
||||
result []Result
|
||||
time time.Duration
|
||||
ok bool
|
||||
}
|
||||
|
||||
// Invoke the test
|
||||
// Wrap results in a TestResult object
|
||||
func (m *manager) invokeTestCase(testCase *TestCase, wg *sync.WaitGroup) {
|
||||
startTime := time.Now()
|
||||
if m.runTestCase(testCase) {
|
||||
testCase.time = time.Since(startTime)
|
||||
testCase.ok = true
|
||||
fmt.Printf("Test OK in %.6fs\n", testCase.time.Seconds())
|
||||
} else {
|
||||
testCase.time = time.Since(startTime)
|
||||
testCase.ok = false
|
||||
fmt.Printf("Test failed in %.6fs\n", testCase.time.Seconds())
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func (m *manager) runTestCase(test *TestCase) bool {
|
||||
fmt.Printf("Test triggered\n")
|
||||
|
||||
Cameradar := m.Config.Cameradar
|
||||
startService(&Cameradar)
|
||||
|
||||
for Cameradar.Active {
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
found := 0
|
||||
toFind := len(test.expected)
|
||||
var validResults []Result
|
||||
if getResult(&test.result, "result.json") {
|
||||
// Check all valid resutls that are supposed to match
|
||||
// Add them to the valid results and leave the failed
|
||||
// ones in the expected slice
|
||||
for _, r := range test.result {
|
||||
index := 0
|
||||
r.Valid = true
|
||||
for _, e := range test.expected {
|
||||
e.Thumb = r.Thumb
|
||||
var err error
|
||||
var addr[] string
|
||||
addr, err = net.LookupHost(e.Address)
|
||||
e.Address = addr[0]
|
||||
if e == r {
|
||||
_, err = os.Stat(r.Thumb)
|
||||
if err == nil {
|
||||
fmt.Println("The result of ", r.Address, " is valid and the thumbnails were generated by Cameradar.")
|
||||
found++
|
||||
validResults = Extend(validResults, r)
|
||||
test.expected = append(test.expected[:index], test.expected[index+1:]...)
|
||||
break
|
||||
} else {
|
||||
fmt.Println("The result of ", r.Address, " seemed valid, but the thumbnails could not be generated by Cameradar.")
|
||||
}
|
||||
}
|
||||
index++
|
||||
}
|
||||
}
|
||||
index := 0
|
||||
// If the result did not match the expected but it was supposed to fail
|
||||
// Add it to the valid results and remove it from the expected slice
|
||||
for _, e := range test.expected {
|
||||
if !e.Valid {
|
||||
found++
|
||||
validResults = Extend(validResults, e)
|
||||
test.expected = append(test.expected[:index], test.expected[index+1:]...)
|
||||
break
|
||||
}
|
||||
index++
|
||||
}
|
||||
// If we found all the expected results, return true
|
||||
if found == toFind {
|
||||
return true
|
||||
}
|
||||
test.result = validResults
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *manager) generateConfig(test []Result, DataBase *mysql_db) bool {
|
||||
var config CameradarConfig
|
||||
var db mysql_db
|
||||
|
||||
db.Host = m.Config.Cameradar.DbHost
|
||||
db.Port = m.Config.Cameradar.DbPort
|
||||
db.User = m.Config.Cameradar.DbUser
|
||||
db.Password = m.Config.Cameradar.DbPassword
|
||||
db.Db_name = m.Config.Cameradar.DbName
|
||||
|
||||
for _, t := range test {
|
||||
if len(config.Subnets) > 0 {
|
||||
config.Subnets += ","
|
||||
}
|
||||
config.Subnets += t.Address
|
||||
}
|
||||
config.Mysql_db = db
|
||||
config.Ports = m.Config.Cameradar.Ports
|
||||
config.Rtsp_url_file = m.Config.Cameradar.RoutesPath
|
||||
config.Rtsp_ids_file = m.Config.Cameradar.IdsPath
|
||||
config.Thumbnail_storage_path = m.Config.Cameradar.ThumbPath
|
||||
b, _ := json.Marshal(config)
|
||||
fmt.Println(string(b))
|
||||
err := ioutil.WriteFile("tmp_config", b, 0644)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return false
|
||||
}
|
||||
*DataBase = db
|
||||
return true
|
||||
}
|
||||
|
||||
func Extend(slice []Result, element Result) []Result {
|
||||
n := len(slice)
|
||||
if n == cap(slice) {
|
||||
// Slice is full; must grow.
|
||||
// We double its size and add 1, so if the size is zero we still grow.
|
||||
newSlice := make([]Result, len(slice), 2*len(slice)+1)
|
||||
copy(newSlice, slice)
|
||||
slice = newSlice
|
||||
}
|
||||
slice = slice[0 : n+1]
|
||||
slice[n] = element
|
||||
return slice
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// Data declarations
|
||||
|
||||
// JUnitTestSuites is a collection of JUnit test suites.
|
||||
type JUnitTestSuites struct {
|
||||
XMLName xml.Name `xml:"testsuites"`
|
||||
Suites []JUnitTestSuite
|
||||
}
|
||||
|
||||
// JUnitTestSuite is a single JUnit test suite which may contain many
|
||||
// testcases.
|
||||
type JUnitTestSuite struct {
|
||||
Tests int `xml:"tests,attr"`
|
||||
Failures int `xml:"failures,attr"`
|
||||
Time string `xml:"time,attr"`
|
||||
TestCases []JUnitTestCase
|
||||
}
|
||||
|
||||
// JUnitTestCase is a single test case with its result.
|
||||
type JUnitTestCase struct {
|
||||
Message string `xml:"message,attr"`
|
||||
Time string `xml:"time,attr"`
|
||||
Failure *JUnitFailure `xml:"failure,omitempty"`
|
||||
}
|
||||
|
||||
// JUnitFailure contains data related to a failed test.
|
||||
type JUnitFailure struct {
|
||||
Message string `xml:"message,attr"`
|
||||
Type string `xml:"type,attr"`
|
||||
Contents string `xml:",chardata"`
|
||||
}
|
||||
|
||||
func (m *manager) WriteResults(result TestCase, output string) bool {
|
||||
fmt.Printf("Displaying results...\n")
|
||||
// Write Console report
|
||||
m.writeConsoleReport(result)
|
||||
|
||||
// Write XML report
|
||||
// Open xml
|
||||
file, err := os.OpenFile(output, os.O_RDONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("Error opening XML: %s\n", err)
|
||||
return false
|
||||
}
|
||||
defer file.Close()
|
||||
err = m.writeJUnitReportXML(result, file, output)
|
||||
if err != nil {
|
||||
fmt.Printf("Error writing XML: %s\n", err)
|
||||
return false
|
||||
}
|
||||
fmt.Printf("-> JUnit XML report written: %s\n", output)
|
||||
return true
|
||||
}
|
||||
|
||||
// Write tests results under JUnit format on w
|
||||
func (m *manager) writeJUnitReportXML(result TestCase, r io.ReadWriter, output string) error {
|
||||
suites := JUnitTestSuites{}
|
||||
dec := xml.NewDecoder(r)
|
||||
if err := dec.Decode(&suites); err != nil {
|
||||
fmt.Printf("\nUnable to deserialize XML log file: %s\n", err)
|
||||
}
|
||||
ts := JUnitTestSuite{
|
||||
Tests: len(result.result) + len(result.expected),
|
||||
Failures: 0,
|
||||
Time: fmt.Sprintf("%.6f", result.time.Seconds()),
|
||||
TestCases: []JUnitTestCase{},
|
||||
}
|
||||
// Run throught all iterations
|
||||
testCase := JUnitTestCase{
|
||||
Time: fmt.Sprintf("%.6f", result.time.Seconds()),
|
||||
Failure: nil,
|
||||
}
|
||||
if len(result.result) > 0 {
|
||||
testCase.Message = "These streams matched what we expected:"
|
||||
}
|
||||
for _, success := range result.result {
|
||||
testCase.Message += " " + success.Address
|
||||
}
|
||||
if !result.ok {
|
||||
testCase.Failure = &JUnitFailure{
|
||||
Message: "These streams did not match what we expected:",
|
||||
Type: "",
|
||||
}
|
||||
}
|
||||
for _, fail := range result.expected {
|
||||
ts.Failures++
|
||||
testCase.Failure.Message += " " + fail.Address
|
||||
}
|
||||
ts.TestCases = append(ts.TestCases, testCase)
|
||||
|
||||
suites.Suites = append(suites.Suites, ts)
|
||||
// Fix indent
|
||||
bytes, err := xml.MarshalIndent(suites, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Write in param stream
|
||||
|
||||
w, err := os.OpenFile(output, os.O_WRONLY|os.O_TRUNC, 0644)
|
||||
writer := io.Writer(w)
|
||||
writer.Write(bytes)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *manager) writeConsoleReport(result TestCase) bool {
|
||||
min := 50 * time.Hour
|
||||
max := 0 * time.Second
|
||||
total := 0 * time.Second
|
||||
successCount := 0
|
||||
failureCount := 0
|
||||
if result.ok {
|
||||
successCount++
|
||||
total += result.time
|
||||
if result.time < min {
|
||||
min = result.time
|
||||
}
|
||||
if result.time > max {
|
||||
max = result.time
|
||||
}
|
||||
} else {
|
||||
failureCount++
|
||||
}
|
||||
fmt.Println("--- Test summary ---\n")
|
||||
if successCount > 0 {
|
||||
fmt.Printf("Results: %d/%d (%d%%)\n", successCount, successCount+failureCount, successCount*100/(successCount+failureCount))
|
||||
fmt.Printf("Total time: %.6fs\n", total.Seconds())
|
||||
fmt.Printf("Average time: %.6fs\n", total.Seconds()/float64(successCount))
|
||||
fmt.Printf("Min time: %.6fs\n", min.Seconds())
|
||||
fmt.Printf("Max time: %.6fs\n", max.Seconds())
|
||||
} else {
|
||||
fmt.Printf("No test in success\n")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user