Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2564943ae7 | |||
| 74b4590758 | |||
| 9e9c1ba5b6 | |||
| 9d78e84dc0 | |||
| ecd318d0c2 | |||
| 58b101ed60 | |||
| 7e6c501582 | |||
| d9a221f9c6 | |||
| c10525b50e | |||
| 67b118a82e | |||
| 1f5db9baa0 | |||
| 4ef463d8a9 | |||
| ae3329bd25 | |||
| 46e17bb0ee | |||
| 27b296c9d2 | |||
| 006c0139be | |||
| 63119d3ff3 | |||
| 5859e9c595 | |||
| d0220ceb7f | |||
| 064a6ff588 | |||
| 9a269bfe0e | |||
| c44b933a83 | |||
| 1f5e9fc502 | |||
| 08231074b9 | |||
| c6d801750e | |||
| 2cf49a8db4 | |||
| 4fba8a8594 | |||
| 76365e3a07 | |||
| e6a38af241 | |||
| a4ad49c1a7 | |||
| 0ac1046138 | |||
| eef9c6f562 | |||
| cf18d869e0 | |||
| 4017429835 | |||
| 615f14d614 |
+15
@@ -26,3 +26,18 @@
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
# Results
|
||||
result.json
|
||||
test-results.xml
|
||||
|
||||
# Build
|
||||
build/
|
||||
|
||||
# JetBrains
|
||||
.idea/
|
||||
|
||||
# Deps
|
||||
deps/boost/
|
||||
deps/jsoncpp/
|
||||
mysql-connector/
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
# Cameradar Changelog
|
||||
|
||||
This file lists all versions of the repository and precises all changes.
|
||||
|
||||
## v1.1.0
|
||||
|
||||
#### Major changes :
|
||||
* There are more command line options
|
||||
* Port can now be overridden in the command line
|
||||
* Subnet can now be overridden in the command line
|
||||
* Bruteforce is now multithreaded and will use as many threads as there are discovered cameras
|
||||
* Thumbnail generation is now multithreaded and will use as many threads as there are discovered cameras
|
||||
* There are now default configuration values in order to make cameradar easier to use
|
||||
|
||||
#### Minor changes :
|
||||
* The algorithms take external input into account (so that a 3rd party can change the DB to help Cameradar in real-time) and thus check the persistent data at each iteration
|
||||
* The default log level is now DEBUG instead of INFO
|
||||
* The bruteforce logs are now INFO instead of DEBUG
|
||||
* The thumbnail generation logs are now INFO instead of DEBUG
|
||||
|
||||
#### Bugs fixed
|
||||
* Fixed a bug in which the MySQL cache manager would consider a camera with known ids as having a valid path even if it weren't
|
||||
* Fixed a bug in which TCP RTSP streams would not generate thumbnails
|
||||
|
||||
## v1.0.5
|
||||
|
||||
* Fixed error in MySQL Cache Manager in which thumbnail generation on valid streams could not be done
|
||||
* Fixed potential crash in the case the machine running cameradar has no memory left to allocate space for the dynamic cache manager
|
||||
|
||||
## v1.0.4
|
||||
|
||||
#### Bugs fixed :
|
||||
|
||||
* Fixed nmap package detection
|
||||
|
||||
## v1.0.3
|
||||
|
||||
#### Bugs fixed :
|
||||
|
||||
* Corrected GStreamer check
|
||||
|
||||
## v1.0.2
|
||||
|
||||
#### Bugs fixed :
|
||||
|
||||
* Fixed issues in MySQL Cache Manager
|
||||
|
||||
#### Minor changes :
|
||||
|
||||
* Added useful debug logs
|
||||
|
||||
## v1.0.1
|
||||
|
||||
### Ubuntu 16.04 Release
|
||||
|
||||
#### Major changes :
|
||||
|
||||
* The Docker deployment is now done using Ubuntu 16.04 instead of Ubuntu 15.10, so that it uses more recent packages.
|
||||
|
||||
#### Minor changes :
|
||||
|
||||
* Removed useless dependencies
|
||||
|
||||
## v1.0.0
|
||||
|
||||
### First production-ready release
|
||||
|
||||
#### Major changes :
|
||||
|
||||
* Added functional testing
|
||||
|
||||
## v0.2.2
|
||||
|
||||
After doing some testing on a weirdly configured camera network in a far away Datacenter, I discovered that some Cameras needed a few tweaks to the Cameradar bruteforcing method in order to be accessed.
|
||||
|
||||
#### Major changes :
|
||||
|
||||
* Cameradar can access Cameras that are configured to always send 400 Bad Requests responses
|
||||
|
||||
#### Minor changes :
|
||||
|
||||
* Changed iterator name from `it` to `stream` in dumb cache manager to improve code readability
|
||||
|
||||
#### Bugfixes :
|
||||
|
||||
* Cameradar no longer considers a timing out Camera as an accessible stream
|
||||
|
||||
## v0.2.1
|
||||
|
||||
This package adds fixes the Docker deployment package.
|
||||
|
||||
#### Minor changes
|
||||
|
||||
* Fixed the Docker deployment package
|
||||
* Updated README
|
||||
|
||||
## v0.2.0
|
||||
|
||||
### MySQL Cache Manager Release
|
||||
|
||||
This package adds a new cache manager using a MySQL database, that can store the results between mutiple uses.
|
||||
|
||||
#### Major changes
|
||||
|
||||
* Added a MySQL Cache Manager
|
||||
|
||||
#### Minor changes
|
||||
|
||||
* Removed legacy code
|
||||
* Removed boost dependency
|
||||
* Improved debugging logs
|
||||
|
||||
## v0.1.1
|
||||
|
||||
### Docker release
|
||||
|
||||
This package adds a way to deploy Cameradar using Docker.
|
||||
|
||||
#### Major changes
|
||||
|
||||
* Added a quick Docker deployment process
|
||||
* Added automatic dependencies downloading through CMake for the manual installation
|
||||
* Added CPack packaging for the Docker deployment
|
||||
|
||||
#### Minor changes
|
||||
|
||||
* Changed recommended cloning method to HTTPS
|
||||
* Added lots of informations to README.md
|
||||
|
||||
## v0.1.0
|
||||
|
||||
This package was the first OpenSource version of Cameradar. It contained only a simple cache manager and had some bugs.
|
||||
+25
-16
@@ -19,10 +19,11 @@ set (PROJECT_NAME cameradar)
|
||||
|
||||
project (${PROJECT_NAME})
|
||||
|
||||
set (${PROJECT_NAME}_VERSION_MAJOR 0)
|
||||
message ("Here")
|
||||
|
||||
set (${PROJECT_NAME}_VERSION_MAJOR 1)
|
||||
set (${PROJECT_NAME}_VERSION_MINOR 1)
|
||||
set (${PROJECT_NAME}_VERSION_PATCH 0)
|
||||
set (${PROJECT_NAME}_SUFFIX "-beta")
|
||||
set (${PROJECT_NAME}_VERSION "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}.${${PROJECT_NAME}_VERSION_PATCH}${${PROJECT_NAME}_SUFFIX}")
|
||||
|
||||
find_package(Git REQUIRED)
|
||||
@@ -83,29 +84,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 +124,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")
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 220 KiB |
@@ -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
|
||||
@@ -14,7 +18,9 @@
|
||||
|
||||
#### And all of this in a _single command-line_.
|
||||
|
||||
Of course, you can also call for individual tasks if you plug in a Database to Cameradar, but for now this repo only contains a basic cache manager. You can however create your own by following the simple example of the **dumb cache manager**.
|
||||
Of course, you can also call for individual tasks if you plug in a Database to Cameradar using the MySQL cache manager for example. You can create your own cache manager by following the simple example of the **dumb cache manager**.
|
||||
|
||||
<p align="center"><img src="https://raw.githubusercontent.com/EtixLabs/cameradar/master/Cameradar.png" width="350"/></p>
|
||||
|
||||
## Table of content
|
||||
|
||||
@@ -38,20 +44,22 @@ 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
|
||||
5. Run `docker-compose up cameradar` to launch Cameradar
|
||||
|
||||
By default, the version of the package in the deployment should be the last stable release.
|
||||
|
||||
If you want to deploy your custom version of Cameradar using the same method, you should check the [advanced docker deployment](#advanced-docker-deployment) tutorial here.
|
||||
|
||||
## Manual installation
|
||||
@@ -63,43 +71,63 @@ 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`
|
||||
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`
|
||||
5. This should compile Cameradar. Go into the `cameradar_standalone` directory
|
||||
6. You can now customize the `conf/cameradar.conf.json` file to set the subnetworks and specific ports you want to scan, as well as the thumbnail generation path. More information will be given about the configuration file in another part of this document.
|
||||
7. You are now ready to launch Cameradar by launching `./cameradar` in the cameradar_standalone directory.
|
||||
1. `git clone https://github.com/EtixLabs/cameradar.git`
|
||||
2. `mkdir build`
|
||||
3. `cd build`
|
||||
3. `cmake ..`
|
||||
4. `make`
|
||||
5. `cd cameradar_standalone`
|
||||
6. `./cameradar -s the_subnet_you_want_to_scan`
|
||||
|
||||
## Advanced Docker deployment
|
||||
|
||||
In case you want to use Docker to deploy your custom version of Cameradar.
|
||||
|
||||
### Dependencies
|
||||
|
||||
The only dependencies are `docker` and `docker-compose`.
|
||||
|
||||
### Deploy a custom version of Cameradar
|
||||
### Using the package generation script
|
||||
1. `git clone https://github.com/EtixLabs/cameradar.git`
|
||||
2. `cd deployment`
|
||||
3. `rm *.tar.gz`
|
||||
4. `./build_last_package.sh`
|
||||
5. `docker-compose build cameradar`
|
||||
6. `docker-compose up cameradar`
|
||||
|
||||
2. Go into the Cameradar repository, create a directory named `build` and go in it
|
||||
3. In the build directory, run `cmake .. -DCMAKE_BUILD_TYPE=Release` This will generate the Makefiles you need to build Cameradar
|
||||
4. Run the command `make package` to compile it into a package
|
||||
5. Copy your package into the `deployment` directory
|
||||
6. Run `docker-compose build cameradar` to build the cameradar container using your custom package
|
||||
5. Run `docker-compose up cameradar` to launch Cameradar
|
||||
### Deploy a custom version of Cameradar by hand
|
||||
|
||||
1. `git clone https://github.com/EtixLabs/cameradar.git`
|
||||
2. `cd build`
|
||||
3. `cmake .. -DCMAKE_BUILD_TYPE=Release`
|
||||
4. `make package`
|
||||
5. `cp cameradar_*_Release_Linux.tar.gz ../deployment`
|
||||
6. `cd ../deployment`
|
||||
7. `docker-compose build cameradar`
|
||||
8. `docker-compose up cameradar`
|
||||
|
||||
### Configuration
|
||||
|
||||
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",
|
||||
@@ -110,20 +138,39 @@ Here is the basic content of the configuration file with simple placeholders :
|
||||
}
|
||||
```
|
||||
|
||||
This **configuration is needed only if you want to overwrite the default values**, which are :
|
||||
|
||||
```json
|
||||
{
|
||||
"subnets" : "localhost",
|
||||
"ports" : "554,8554",
|
||||
"rtsp_url_file" : "conf/url.json",
|
||||
"rtsp_ids_file" : "conf/ids.json",
|
||||
"thumbnail_storage_path" : "/tmp",
|
||||
"cache_manager_path" : "../cache_managers/dumb_cache_manager",
|
||||
"cache_manager_name" : "dumb"
|
||||
}
|
||||
```
|
||||
|
||||
This means that **by default Cameradar will not use a database**, will scan localhost and the ports 554 (default RTSP port) and 8554 (default emulated RTSP port), use the default constructor dictionaries and store the thumbnails in `/tmp`. If you need to override simply the subnets or ports, you can use the [command line options](#command-line-options).
|
||||
|
||||
The subnetworks should be passed separated by commas only, and their subnet format should be the same as used in nmap.
|
||||
```json
|
||||
"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
|
||||
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 **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 repository 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.
|
||||
|
||||
The cache manager path and name variables are used to change the cache manager you want to load into Cameradar. If you want to, you can code your own cache manager using a database, a file, a remote server, [...]. Feel free to share it by creating a merge request on this repo if you developed a generic manager (It must not be specific to your company's infrastructure).
|
||||
The cache manager path and name variables are used to change the cache manager you want to load into Cameradar. If you want to, you can code your own cache manager using a database, a file, a remote server, [...]. Feel free to share it by creating a merge request on this repository if you developed a generic manager (It must not be specific to your company's infrastructure).
|
||||
|
||||
## Output
|
||||
|
||||
For each camera, Cameradar will output these JSON objects :
|
||||
|
||||
```json
|
||||
{
|
||||
"address" : "173.16.100.45",
|
||||
@@ -143,7 +190,7 @@ The cache manager path and name variables are used to change the cache manager y
|
||||
|
||||
## Check camera access
|
||||
|
||||
If you have vlc, you should be able to use the GUI to connect to the RTSP stream using this format : `username:password@address:port/route`
|
||||
If you have [VLC Media Player](http://www.videolan.org/vlc/), you should be able to use the GUI to connect to the RTSP stream using this format : `username:password@address:port/route`
|
||||
|
||||
With the above result, the RTSP URL would be `admin:123456@173.16.100.45:554/live.sdp`
|
||||
|
||||
@@ -152,6 +199,9 @@ If you're still in your console however, you can go even faster by using **vlc i
|
||||
## Command line options
|
||||
|
||||
* **"-c"** : Set a custom path to the configuration file (-c /path/to/conf)
|
||||
* **"-s"** : Set custom subnets (overrides configuration)
|
||||
* **"-p"** : Set custom ports (overrides configuration)
|
||||
* **"-m"** : Set number of threads (*Default value : 1*)
|
||||
* **"-l"** : Set log level
|
||||
* **"-l 1"** : Log level DEBUG
|
||||
* _Will print everything including debugging logs_
|
||||
@@ -197,18 +247,19 @@ The output of Cameradar will be printed on the standard output and will also be
|
||||
|
||||
## Contribution
|
||||
|
||||
Well there are many things we could code in order to add features to Cameradar. Adding other protocols than RTSP would be really cool, as well as making generic cache managers. Creating an HTTP server with an API that would launch cameradar upon recieving requests ans answer with Cameradar's result would also be potentially really useful.
|
||||
Well there are many things we could code in order to add features to Cameradar. Adding other protocols than RTSP would be really cool, as well as making more generic cache managers. Improving Cameradar's performance or even the deployment could also be a great help!
|
||||
|
||||
If you're not into software development or not into C++, even updating the dictionaries would be a really cool contribution! Just make sure the ids and routes you add are **default constructor credentials** and not custom credentials.
|
||||
|
||||
If you have other cool ideas, feel free to share them with me at brendan.leglaunec@etixgroup.com !
|
||||
If you have other cool ideas, feel free to share them with me at [brendan.leglaunec@etixgroup.com](mailto:brendan.leglaunec@etixgroup.com) !
|
||||
|
||||
## 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
|
||||
- [ ] Make a standalone docker image
|
||||
- [ ] Push to DockerHub
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -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 {
|
||||
@@ -29,6 +29,8 @@ private:
|
||||
std::vector<etix::cameradar::stream_model> streams;
|
||||
std::shared_ptr<etix::cameradar::configuration> configuration;
|
||||
|
||||
std::mutex m;
|
||||
|
||||
public:
|
||||
using cache_manager_base::cache_manager_base;
|
||||
~dumb_cache_manager();
|
||||
@@ -38,13 +40,15 @@ public:
|
||||
bool load_dumb_conf(std::shared_ptr<etix::cameradar::configuration> configuration);
|
||||
bool configure(std::shared_ptr<etix::cameradar::configuration> configuration) override;
|
||||
|
||||
bool has_changed(const etix::cameradar::stream_model&);
|
||||
|
||||
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() 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();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,39 +47,53 @@ dumb_cache_manager::load_dumb_conf(std::shared_ptr<etix::cameradar::configuratio
|
||||
//! parameter
|
||||
void
|
||||
dumb_cache_manager::set_streams(std::vector<etix::cameradar::stream_model> model) {
|
||||
std::lock_guard<std::mutex> lock(m);
|
||||
this->streams = 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; }
|
||||
std::lock_guard<std::mutex> lock(m);
|
||||
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 (stream.ids_found && stream.path_found) ret.push_back(stream);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Returns true if the stream passed as a parameter has changed in the cache
|
||||
bool
|
||||
dumb_cache_manager::has_changed(const etix::cameradar::stream_model& old) {
|
||||
for (const auto& stream : this->streams) {
|
||||
if (stream.address == old.address)
|
||||
if (stream.path_found != old.path_found || stream.ids_found != old.ids_found)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
cache_manager_iface*
|
||||
cache_manager_instance_new() {
|
||||
|
||||
@@ -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,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 <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;
|
||||
|
||||
std::mutex m;
|
||||
|
||||
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;
|
||||
|
||||
bool has_changed(const etix::cameradar::stream_model&);
|
||||
|
||||
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,292 @@
|
||||
// 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 (const 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");
|
||||
std::lock_guard<std::mutex> lock(m);
|
||||
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 (result.data->next()) continue;
|
||||
|
||||
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());
|
||||
std::lock_guard<std::mutex> lock(m);
|
||||
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("path_found"),
|
||||
result.data->getBoolean("ids_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("path_found"),
|
||||
result.data->getBoolean("ids_found"), result.data->getString("thumbnail_path")
|
||||
};
|
||||
lst.push_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
delete result.data;
|
||||
return lst;
|
||||
}
|
||||
|
||||
// Returns true if the stream passed as a parameter has changed in the cache
|
||||
bool
|
||||
mysql_cache_manager::has_changed(const etix::cameradar::stream_model& old) {
|
||||
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 {};
|
||||
}
|
||||
|
||||
while (result.data->next()) {
|
||||
if (result.data->getString("address") == old.address)
|
||||
if (result.data->getBoolean("ids_found") != old.ids_found ||
|
||||
result.data->getBoolean("path_found") != old.path_found)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"/camera.stm",
|
||||
"/ch0",
|
||||
"/ch001.sdp",
|
||||
"/ch01.264",
|
||||
"/ch0_unicast_firststream",
|
||||
"/ch0_unicast_secondstream",
|
||||
"/channel1",
|
||||
|
||||
@@ -14,38 +14,42 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <stream_model.h>
|
||||
#include <configuration.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stream_model.h>
|
||||
#include <vector>
|
||||
|
||||
namespace etix {
|
||||
namespace cameradar {
|
||||
|
||||
//! The interface a cache_manager should implement to be valid
|
||||
// The interface a cache_manager should implement to be valid
|
||||
class cache_manager_iface {
|
||||
public:
|
||||
virtual ~cache_manager_iface() {}
|
||||
|
||||
//! Launches the manager configuration
|
||||
//! \return false if failed
|
||||
// Launches the manager configuration
|
||||
// \return false if failed
|
||||
virtual bool configure(std::shared_ptr<etix::cameradar::configuration> configuration) = 0;
|
||||
|
||||
//! get the name of the cache manager
|
||||
// get the name of the cache manager
|
||||
virtual const std::string& get_name() const = 0;
|
||||
|
||||
//! Replaces all cached streams by the content of the vector given as
|
||||
//! parameter
|
||||
// Replaces all cached streams by the content of the vector given as
|
||||
// parameter
|
||||
virtual void set_streams(std::vector<etix::cameradar::stream_model> model) = 0;
|
||||
|
||||
//! Inserts a single stream to the cache
|
||||
// Inserts a single stream to the cache
|
||||
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;
|
||||
// Returns true if the stream passed as a parameter has changed in the cache
|
||||
virtual bool has_changed(const etix::cameradar::stream_model&) = 0;
|
||||
|
||||
//! Gets all valid streams which have been accessed
|
||||
virtual std::vector<etix::cameradar::stream_model> get_valid_streams() const = 0;
|
||||
// Gets all cached streams
|
||||
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() = 0;
|
||||
};
|
||||
|
||||
class cache_manager_base : public cache_manager_iface {
|
||||
@@ -53,27 +57,30 @@ public:
|
||||
cache_manager_base() = default;
|
||||
virtual ~cache_manager_base() = default;
|
||||
|
||||
//! Launches the cache manager configuration
|
||||
//! \return false if failed
|
||||
// Launches the cache manager configuration
|
||||
// \return false if failed
|
||||
virtual bool configure(std::shared_ptr<etix::cameradar::configuration> configuration) = 0;
|
||||
|
||||
//! get the name of the cache manager
|
||||
// get the name of the cache manager
|
||||
virtual const std::string& get_name() const = 0;
|
||||
|
||||
//! Replaces all cached streams by the content of the vector given as
|
||||
//! parameter
|
||||
// Replaces all cached streams by the content of the vector given as
|
||||
// parameter
|
||||
virtual void set_streams(std::vector<etix::cameradar::stream_model> model) = 0;
|
||||
|
||||
//! Updates a single stream to the cache
|
||||
// Returns true if the stream passed as a parameter has changed in the cache
|
||||
virtual bool has_changed(const etix::cameradar::stream_model&) = 0;
|
||||
|
||||
// Updates a single stream to the cache
|
||||
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;
|
||||
// Gets all cached streams
|
||||
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;
|
||||
// Gets all valid streams which have been accessed
|
||||
virtual std::vector<etix::cameradar::stream_model> get_valid_streams() = 0;
|
||||
|
||||
//! Get the manager's instance
|
||||
// Get the manager's instance
|
||||
cache_manager_base& get_instance();
|
||||
|
||||
template <typename I, typename T>
|
||||
@@ -87,59 +94,62 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
//! The representation of a cache manager
|
||||
//!
|
||||
//! This class loads a shared library, and tries to call an extern "C"
|
||||
//! function which should instanciate a new instance of the plugin.
|
||||
// The representation of a cache manager
|
||||
//
|
||||
// This class loads a shared library, and tries to call an extern "C"
|
||||
// function which should instanciate a new instance of the plugin.
|
||||
class cache_manager {
|
||||
private:
|
||||
static const std::string PLUGIN_EXT;
|
||||
static const std::string default_symbol;
|
||||
|
||||
//! the name of the cache manager
|
||||
// The name of the cache manager
|
||||
std::string name;
|
||||
|
||||
//! The path where the manager is located
|
||||
//! should be specified in the configuration file
|
||||
// The write mutex to avoid conflicts when multithreading
|
||||
std::mutex m;
|
||||
|
||||
// The path where the manager is located
|
||||
// should be specified in the configuration file
|
||||
std::string path;
|
||||
|
||||
//! The symbol entry point of the manager to
|
||||
//! call to create an instance from the shared library
|
||||
// The symbol entry point of the manager to
|
||||
// call to create an instance from the shared library
|
||||
std::string symbol;
|
||||
|
||||
//! The handle to the shared library where is stored the manager
|
||||
// The handle to the shared library where is stored the manager
|
||||
void* handle = nullptr;
|
||||
|
||||
//! The cache manager instance if it is successfully loaded
|
||||
// The cache manager instance if it is successfully loaded
|
||||
cache_manager_iface* ptr = nullptr;
|
||||
|
||||
//! Internal function that creates the full path of the cache manager
|
||||
//!
|
||||
//! full path is composed of: the path, the name, the string "_cache-manager"
|
||||
//! and the extension PLUGIN_EXT depending of the platform
|
||||
// Internal function that creates the full path of the cache manager
|
||||
//
|
||||
// full path is composed of: the path, the name, the string "_cache-manager"
|
||||
// and the extension PLUGIN_EXT depending of the platform
|
||||
std::string make_full_path();
|
||||
|
||||
public:
|
||||
//! Delete constructor
|
||||
// Delete constructor
|
||||
cache_manager() = delete;
|
||||
|
||||
//! The manager needs a path and a symbol to be instantiated.
|
||||
//! The symbol can be changed if the plugin entry point
|
||||
//! is different than the standard one.
|
||||
// The manager needs a path and a symbol to be instantiated.
|
||||
// The symbol can be changed if the plugin entry point
|
||||
// is different than the standard one.
|
||||
cache_manager(const std::string& path,
|
||||
const std::string& name,
|
||||
const std::string& symbol = default_symbol);
|
||||
|
||||
// //! Copy constructor
|
||||
// // Copy constructor
|
||||
// cache_manager(cache_manager &other);
|
||||
|
||||
//! Move constructor
|
||||
// Move constructor
|
||||
cache_manager(cache_manager&& old);
|
||||
|
||||
~cache_manager();
|
||||
|
||||
//! Creates the instance of the cache_manager
|
||||
//!
|
||||
// Creates the instance of the cache_manager
|
||||
//
|
||||
// \return false if the cache_manager failed to be instantiated or if
|
||||
// the cache_manager is not a valid cache manager, true otherwise
|
||||
bool make_instance();
|
||||
@@ -152,23 +162,23 @@ public:
|
||||
return this->get<I, T>();
|
||||
}
|
||||
|
||||
//! Helper to access internal loaded cache_manager
|
||||
//!
|
||||
//! Gives access to the methods of the cache_manager using the operator
|
||||
//! -> (e.g.: cache_manager->get_name());
|
||||
// Helper to access internal loaded cache_manager
|
||||
//
|
||||
// Gives access to the methods of the cache_manager using the operator
|
||||
// -> (e.g.: cache_manager->get_name());
|
||||
cache_manager_iface* operator->();
|
||||
const cache_manager_iface* operator->() const;
|
||||
|
||||
//! helper function to check if a cache_manager is instantiated or not
|
||||
// helper function to check if a cache_manager is instantiated or not
|
||||
friend bool operator==(std::nullptr_t nullp, const cache_manager& p);
|
||||
|
||||
//! helper function to check if a cache_manager is instantiated or not
|
||||
// helper function to check if a cache_manager is instantiated or not
|
||||
friend bool operator==(const cache_manager& p, std::nullptr_t nullp);
|
||||
|
||||
//! helper function to check if a cache_manager is instantiated or not
|
||||
// helper function to check if a cache_manager is instantiated or not
|
||||
friend bool operator!=(std::nullptr_t nullp, const cache_manager& p);
|
||||
|
||||
//! helper function to check if a cache_manager is instantiated or not
|
||||
// helper function to check if a cache_manager is instantiated or not
|
||||
friend bool operator!=(const cache_manager& p, std::nullptr_t nullp);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,19 +14,26 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <json/reader.h> // Json::Value
|
||||
#include <json/value.h> // Json::Value
|
||||
#include <logger.h> // _LOG_
|
||||
#include <opt_parse.h> // parsing opt
|
||||
#include <string> // std::string
|
||||
#include <utility> // std::pair
|
||||
#include <logger.h> // _LOG_
|
||||
#include <json/value.h> // Json::Value
|
||||
#include <json/reader.h> // Json::Value
|
||||
|
||||
namespace etix {
|
||||
|
||||
namespace cameradar {
|
||||
|
||||
static const std::string default_configuration_path = "conf/cameradar.conf.json";
|
||||
static const std::string default_ids_file_path_ = "conf/ids.json";
|
||||
static const std::string default_urls_file_path_ = "conf/url.json";
|
||||
|
||||
static const std::string default_ports = "554,8554";
|
||||
static const std::string default_subnets = "localhost,168.0.0.0/24";
|
||||
static const std::string default_thumbnail_storage_path = "/tmp";
|
||||
static const std::string default_rtsp_url_file = "conf/url.json";
|
||||
static const std::string default_rtsp_ids_file = "conf/ids.json";
|
||||
static const std::string default_cache_manager_path = "../cache_managers/dumb_cache_manager";
|
||||
static const std::string default_cache_manager_name = "dumb";
|
||||
|
||||
struct configuration {
|
||||
std::string thumbnail_storage_path;
|
||||
@@ -49,7 +56,7 @@ struct configuration {
|
||||
const std::string& rtsp_ids_file,
|
||||
const std::string& cache_manager_path,
|
||||
const std::string& cache_manager_name,
|
||||
const std::string& ports = "1-65535")
|
||||
const std::string& ports)
|
||||
: thumbnail_storage_path(thumbnail_storage_path)
|
||||
, subnets(subnets)
|
||||
, rtsp_url_file(rtsp_url_file)
|
||||
@@ -67,6 +74,6 @@ struct configuration {
|
||||
};
|
||||
|
||||
std::pair<bool, std::string> read_file(const std::string& path);
|
||||
std::pair<bool, configuration> load(const std::string& path);
|
||||
std::pair<bool, configuration> load(const std::pair<bool, etix::tool::opt_parse>& args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -32,8 +32,8 @@ std::string decode64(const std::string& str_to_decode);
|
||||
std::string base64_encode(unsigned char const*, unsigned int len);
|
||||
std::string base64_decode(std::string const& s);
|
||||
|
||||
} //! encode
|
||||
} // encode
|
||||
|
||||
} //! tool
|
||||
} // tool
|
||||
|
||||
} //! etix
|
||||
} // etix
|
||||
|
||||
@@ -24,8 +24,8 @@ namespace tool {
|
||||
|
||||
static std::mutex mutex;
|
||||
|
||||
//! Format a string with the given arguments
|
||||
//! same behavior as sprintf.
|
||||
// Format a string with the given arguments
|
||||
// same behavior as sprintf.
|
||||
template <class... Args>
|
||||
std::string
|
||||
fmt(const std::string& base, Args... args) {
|
||||
|
||||
@@ -34,8 +34,8 @@ bool create_folder(const std::string& folder);
|
||||
bool create_recursive_folder(const std::string& folder);
|
||||
std::string home();
|
||||
|
||||
//! this functions take a copy because we need to make some operations on the string
|
||||
//! for example, we need to apply std::string::pop_back
|
||||
// this functions take a copy because we need to make some operations on the string
|
||||
// for example, we need to apply std::string::pop_back
|
||||
std::string get_file_folder(std::string full_file_path);
|
||||
|
||||
bool copy(const std::string& src, const std::string& dst);
|
||||
|
||||
@@ -23,20 +23,13 @@ namespace etix {
|
||||
|
||||
namespace tool {
|
||||
|
||||
//! Parse command line arguments
|
||||
class opt_parse {
|
||||
private:
|
||||
//! An argumetn representation to be passed to the program
|
||||
struct opt_param {
|
||||
//! is it required
|
||||
bool required;
|
||||
//! Does he needs arguments
|
||||
bool need_arg;
|
||||
//! What is its name
|
||||
std::string name;
|
||||
//! Description
|
||||
std::string desc;
|
||||
//! the argument of the arguments !
|
||||
std::string argument;
|
||||
bool is_passed = false;
|
||||
|
||||
@@ -44,22 +37,14 @@ private:
|
||||
: required(required), need_arg(need_arg), name(name), desc(desc) {}
|
||||
};
|
||||
|
||||
//! Map of the different possibles argument as string and their
|
||||
//! rertpresntation
|
||||
std::unordered_map<std::string, opt_param> params;
|
||||
//! The total count of arguments for this program
|
||||
int argc;
|
||||
//! The list of arguments as a String
|
||||
char** argv;
|
||||
//! The total count of params
|
||||
int params_cnt = 0;
|
||||
|
||||
public:
|
||||
//! An iterator to iterate over all the arguments of
|
||||
//! the program
|
||||
class iterator {
|
||||
private:
|
||||
//! The arguments vector and their argumetns
|
||||
std::vector<std::pair<std::string, std::string>> args;
|
||||
unsigned int opt_pos = 0;
|
||||
|
||||
@@ -71,65 +56,40 @@ public:
|
||||
return *this;
|
||||
}
|
||||
std::pair<std::string, std::string>& operator*() { return this->args.at(this->opt_pos); }
|
||||
bool operator==(const iterator& rhs) const { return this->opt_pos == rhs.opt_pos; }
|
||||
bool operator!=(const iterator& rhs) const { return this->opt_pos != rhs.opt_pos; }
|
||||
bool
|
||||
operator==(const iterator& rhs) const {
|
||||
return this->opt_pos == rhs.opt_pos;
|
||||
}
|
||||
bool
|
||||
operator!=(const iterator& rhs) const {
|
||||
return this->opt_pos != rhs.opt_pos;
|
||||
}
|
||||
};
|
||||
|
||||
opt_parse() = delete;
|
||||
|
||||
//! \param argc Total count of arguements
|
||||
//! \param argv Cmdline arguments from program startup
|
||||
opt_parse(int argc, char* argv[]);
|
||||
|
||||
~opt_parse();
|
||||
|
||||
//! Add a argument required for your program
|
||||
//!
|
||||
//! If the specified argument is not given in cmdline, a error will be
|
||||
//! generated
|
||||
//! \param name The name of the parameter as a string (e.g "-l")
|
||||
//! \param desc A description that will be used by the function `print_help`
|
||||
//! \param need_arg Does the argument require a parameter
|
||||
void required(const std::string& name, const std::string& desc = "", bool need_arg = true);
|
||||
|
||||
//! Add an optional argument for your program
|
||||
//!
|
||||
//! If the specified argument is not given in cmdline, a error will be
|
||||
//! generated
|
||||
//! \param name The name of the parameter as a string (e.g "-l")
|
||||
//! \param desc A description that will be used by the function `print_help`
|
||||
//! \param need_arg Does the argument require a parameter
|
||||
void optional(const std::string& name, const std::string& desc = "", bool need_arg = true);
|
||||
|
||||
//! Process the parsing of the arguments
|
||||
bool execute();
|
||||
|
||||
//! \return an iterator on the begin of the arguments
|
||||
iterator begin() const;
|
||||
|
||||
//! \return the iterator on the end of the arguments
|
||||
iterator end() const;
|
||||
|
||||
//! Print the usage using the parameter setted when referencing the arguments
|
||||
//! for the program
|
||||
void print_usage() const;
|
||||
|
||||
//! Print an help message generated using all the specified arguments
|
||||
void print_help() const;
|
||||
|
||||
//! Is there on the parameters (missing parameter ? unknows ? missing
|
||||
//! arguments ?)
|
||||
//! \return true if there is error, false otherwise
|
||||
bool has_error() const;
|
||||
|
||||
//! Does the option exist or not ?
|
||||
//! \param opt The name of the option to check
|
||||
//! \return true if the param exist, false otherwise
|
||||
bool exist(const std::string& opt) const;
|
||||
|
||||
//! Acces to an argument from its name
|
||||
//! \param opt The name of the option to check
|
||||
//! \return the the argument of the param as a string
|
||||
std::string operator[](const std::string& opt) const;
|
||||
};
|
||||
|
||||
|
||||
@@ -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,9 +14,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cameradar_task.h> // task interface
|
||||
#include <cachemanager.h> // cacheManager
|
||||
#include <cameradar_task.h> // task interface
|
||||
#include <describe.h> // send DESCRIBE through cURL
|
||||
#include <future> // std::async & std::future
|
||||
#include <signal_handler.h> // signals
|
||||
#include <stream_model.h> // data model
|
||||
|
||||
@@ -41,6 +42,7 @@ public:
|
||||
bool test_ids(const etix::cameradar::stream_model& cit,
|
||||
const std::string& pit,
|
||||
const std::string& uit) const;
|
||||
bool bruteforce_camera(const stream_model& stream) const;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cachemanager.h> // cacheManager
|
||||
#include <cameradar_task.h> // task interface
|
||||
#include <memory> // std::shared_ptr
|
||||
#include <logger.h> // LOG
|
||||
#include <curl/curl.h> // cURL client for discovery
|
||||
#include <describe.h> // send DESCRIBE through cURL
|
||||
#include <future> // std::async & std::future
|
||||
#include <logger.h> // LOG
|
||||
#include <memory> // std::shared_ptr
|
||||
#include <signal_handler.h> // signals
|
||||
#include <stream_model.h> // data model
|
||||
#include <cachemanager.h> // cacheManager
|
||||
|
||||
namespace etix {
|
||||
namespace cameradar {
|
||||
@@ -42,6 +43,7 @@ public:
|
||||
virtual bool run() const;
|
||||
|
||||
bool test_path(const etix::cameradar::stream_model& cit, const std::string& it) const;
|
||||
bool bruteforce_camera(const stream_model& stream) const;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -14,13 +14,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cachemanager.h> // cacheManager
|
||||
#include <cameradar_task.h> // task interface
|
||||
#include <fmt.h> // fmt
|
||||
#include <future> // std::async & std::future
|
||||
#include <launch_command.h> // launch_command
|
||||
#include <rtsp_path.h> // make_path
|
||||
#include <signal_handler.h> // signals
|
||||
#include <stream_model.h> // data model
|
||||
#include <cachemanager.h> // cacheManager
|
||||
#include <fmt.h> // fmt
|
||||
#include <rtsp_path.h> // make_path
|
||||
|
||||
namespace etix {
|
||||
namespace cameradar {
|
||||
@@ -41,6 +42,7 @@ public:
|
||||
virtual bool run() const;
|
||||
|
||||
std::string build_output_file_path(const std::string& path) const;
|
||||
bool generate_thumbnail(const stream_model& stream) const;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,11 +31,11 @@ distribution.
|
||||
#pragma warning(disable : 4786)
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
// Help out windows:
|
||||
#if defined(_DEBUG) && !defined(DEBUG)
|
||||
@@ -43,9 +43,9 @@ distribution.
|
||||
#endif
|
||||
|
||||
#ifdef TIXML_USE_STL
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#define TIXML_STRING std::string
|
||||
#else
|
||||
#include "tinystr.h"
|
||||
@@ -1086,9 +1086,18 @@ public:
|
||||
return const_cast<TiXmlAttribute*>((const_cast<const TiXmlAttribute*>(this))->Previous());
|
||||
}
|
||||
|
||||
bool operator==(const TiXmlAttribute& rhs) const { return rhs.name == name; }
|
||||
bool operator<(const TiXmlAttribute& rhs) const { return name < rhs.name; }
|
||||
bool operator>(const TiXmlAttribute& rhs) const { return name > rhs.name; }
|
||||
bool
|
||||
operator==(const TiXmlAttribute& rhs) const {
|
||||
return rhs.name == name;
|
||||
}
|
||||
bool
|
||||
operator<(const TiXmlAttribute& rhs) const {
|
||||
return name < rhs.name;
|
||||
}
|
||||
bool
|
||||
operator>(const TiXmlAttribute& rhs) const {
|
||||
return name > rhs.name;
|
||||
}
|
||||
|
||||
/* Attribute parsing starts: first letter of the name
|
||||
returns: the next char after the
|
||||
@@ -1171,7 +1180,6 @@ private:
|
||||
//*ME: Because of hidden/disabled copy-construktor in TiXmlAttribute
|
||||
//(sentinel-element),
|
||||
//*ME: this class must be also use a hidden/disabled copy-constructor
|
||||
//!!!
|
||||
TiXmlAttributeSet(const TiXmlAttributeSet&); // not allowed
|
||||
void operator=(const TiXmlAttributeSet&); // not allowed (as TiXmlAttribute)
|
||||
|
||||
@@ -1509,7 +1517,8 @@ public:
|
||||
#endif
|
||||
|
||||
TiXmlText(const TiXmlText& copy) : TiXmlNode(TiXmlNode::TINYXML_TEXT) { copy.CopyTo(this); }
|
||||
TiXmlText& operator=(const TiXmlText& base) {
|
||||
TiXmlText&
|
||||
operator=(const TiXmlText& base) {
|
||||
base.CopyTo(this);
|
||||
return *this;
|
||||
}
|
||||
@@ -1664,7 +1673,8 @@ public:
|
||||
TiXmlUnknown(const TiXmlUnknown& copy) : TiXmlNode(TiXmlNode::TINYXML_UNKNOWN) {
|
||||
copy.CopyTo(this);
|
||||
}
|
||||
TiXmlUnknown& operator=(const TiXmlUnknown& copy) {
|
||||
TiXmlUnknown&
|
||||
operator=(const TiXmlUnknown& copy) {
|
||||
copy.CopyTo(this);
|
||||
return *this;
|
||||
}
|
||||
@@ -2039,7 +2049,8 @@ public:
|
||||
TiXmlHandle(TiXmlNode* _node) { this->node = _node; }
|
||||
/// Copy constructor
|
||||
TiXmlHandle(const TiXmlHandle& ref) { this->node = ref.node; }
|
||||
TiXmlHandle operator=(const TiXmlHandle& ref) {
|
||||
TiXmlHandle
|
||||
operator=(const TiXmlHandle& ref) {
|
||||
if (&ref != this) this->node = ref.node;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -56,7 +56,7 @@ configuration::load_ids() {
|
||||
"the default one "
|
||||
"instead.",
|
||||
"configuration");
|
||||
content = read_file(default_ids_file_path_).second;
|
||||
content = read_file(default_rtsp_ids_file).second;
|
||||
}
|
||||
if (content.size()) {
|
||||
auto root = Json::Value();
|
||||
@@ -101,20 +101,12 @@ configuration::load_url() {
|
||||
"the default one "
|
||||
"instead.",
|
||||
"configuration");
|
||||
content = read_file(default_urls_file_path_).second;
|
||||
content = read_file(default_rtsp_url_file).second;
|
||||
}
|
||||
if (content.size()) {
|
||||
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()) {
|
||||
@@ -139,18 +131,47 @@ serialize(const Json::Value& root) {
|
||||
std::pair<bool, configuration> ret;
|
||||
|
||||
try {
|
||||
ret.second.ports = root["ports"].asString();
|
||||
ret.second.subnets = root["subnets"].asString();
|
||||
ret.second.rtsp_ids_file = root["rtsp_ids_file"].asString();
|
||||
ret.second.rtsp_url_file = root["rtsp_url_file"].asString();
|
||||
ret.second.thumbnail_storage_path = root["thumbnail_storage_path"].asString();
|
||||
ret.second.cache_manager_path = root["cache_manager_path"].asString();
|
||||
ret.second.cache_manager_name = root["cache_manager_name"].asString();
|
||||
if (!root["ports"].isNull())
|
||||
ret.second.ports = root["ports"].asString();
|
||||
else
|
||||
ret.second.ports = default_ports;
|
||||
|
||||
if (!root["subnets"].isNull())
|
||||
ret.second.subnets = root["subnets"].asString();
|
||||
else
|
||||
ret.second.subnets = default_subnets;
|
||||
|
||||
if (!root["rtsp_ids_file"].isNull())
|
||||
ret.second.rtsp_ids_file = root["rtsp_ids_file"].asString();
|
||||
else
|
||||
ret.second.rtsp_ids_file = default_rtsp_ids_file;
|
||||
|
||||
if (!root["rtsp_url_file"].isNull())
|
||||
ret.second.rtsp_url_file = root["rtsp_url_file"].asString();
|
||||
else
|
||||
ret.second.rtsp_url_file = default_rtsp_url_file;
|
||||
|
||||
if (!root["thumbnail_storage_path"].isNull())
|
||||
ret.second.thumbnail_storage_path = root["thumbnail_storage_path"].asString();
|
||||
else
|
||||
ret.second.thumbnail_storage_path = default_thumbnail_storage_path;
|
||||
|
||||
if (!root["cache_manager_path"].isNull())
|
||||
ret.second.cache_manager_path = root["cache_manager_path"].asString();
|
||||
else
|
||||
ret.second.cache_manager_path = default_cache_manager_path;
|
||||
|
||||
if (!root["cache_manager_name"].isNull())
|
||||
ret.second.cache_manager_name = root["cache_manager_name"].asString();
|
||||
else
|
||||
ret.second.cache_manager_name = default_cache_manager_name;
|
||||
|
||||
ret.first = true;
|
||||
} catch (std::exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERR_("Configuration failed : " + std::string(e.what()), "configuration");
|
||||
ret.first = false;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -164,7 +185,16 @@ configuration::get_raw() const {
|
||||
// Will return true & valid configuration if success
|
||||
// Otherwise false & empty configuration
|
||||
std::pair<bool, configuration>
|
||||
load(const std::string& path) {
|
||||
load(const std::pair<bool, etix::tool::opt_parse>& args) {
|
||||
std::string path;
|
||||
|
||||
if (not args.second.exist("-c")) {
|
||||
path = etix::cameradar::default_configuration_path;
|
||||
LOG_WARN_("No custom path set, trying to use default path: " + path, "main");
|
||||
} else {
|
||||
path = args.second["-c"];
|
||||
}
|
||||
|
||||
// Check if the file exists at the given path
|
||||
if (access(path.c_str(), F_OK) == -1) {
|
||||
LOG_ERR_("Can't access: " + path, "configuration");
|
||||
@@ -194,12 +224,14 @@ 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();
|
||||
conf.first &= conf.second.load_ids();
|
||||
|
||||
if (args.second.exist("-s")) conf.second.subnets = args.second["-s"];
|
||||
if (args.second.exist("-p")) conf.second.ports = args.second["-p"];
|
||||
|
||||
return conf;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,19 @@
|
||||
namespace etix {
|
||||
namespace cameradar {
|
||||
|
||||
//! Sends a request to the camera using the OPTION method,
|
||||
//! then a DESCRIBE to check for valid IDs
|
||||
//! then another DESCIBE with IDs if an authentication is needed
|
||||
// Ugly workaround
|
||||
size_t
|
||||
write_data(void* buffer, size_t size, size_t nmemb, void* userp) {
|
||||
// I'm sorry for this
|
||||
// Forget you ever saw it
|
||||
(void)buffer;
|
||||
(void)userp;
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
// Sends a request to the camera using the OPTION method,
|
||||
// then a DESCRIBE to check for valid IDs
|
||||
// then another DESCIBE with IDs if an authentication is needed
|
||||
bool
|
||||
curl_describe(const std::string& path, bool logs) {
|
||||
CURL* csession;
|
||||
@@ -27,23 +37,21 @@ curl_describe(const std::string& path, bool logs) {
|
||||
struct curl_slist* custom_msg = NULL;
|
||||
char URL[256];
|
||||
long rc;
|
||||
FILE* protofile = NULL;
|
||||
protofile = fopen("/dev/null", "wb");
|
||||
csession = curl_easy_init();
|
||||
if (csession == NULL) return -1;
|
||||
sprintf(URL, "%s", path.c_str());
|
||||
// These are the options for all following cURL requests
|
||||
// Activate verbose if debug is needed
|
||||
curl_easy_setopt(csession, CURLOPT_NOSIGNAL, 1);
|
||||
curl_easy_setopt(csession, CURLOPT_TIMEOUT, 1);
|
||||
curl_easy_setopt(csession, CURLOPT_NOBODY, 1);
|
||||
curl_easy_setopt(csession, CURLOPT_URL, URL);
|
||||
curl_easy_setopt(csession, CURLOPT_RTSP_STREAM_URI, URL);
|
||||
curl_easy_setopt(csession, CURLOPT_FOLLOWLOCATION, 0);
|
||||
curl_easy_setopt(csession, CURLOPT_HEADER, 0);
|
||||
curl_easy_setopt(csession, CURLOPT_INTERLEAVEDATA, protofile);
|
||||
curl_easy_setopt(csession, CURLOPT_VERBOSE, 0);
|
||||
curl_easy_setopt(csession, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_OPTIONS);
|
||||
curl_easy_setopt(csession, CURLOPT_WRITEDATA, protofile);
|
||||
curl_easy_setopt(csession, CURLOPT_WRITEFUNCTION, write_data);
|
||||
// This request will handshake the stream's server, it should always return 200 OK
|
||||
curl_easy_perform(csession);
|
||||
curl_easy_getinfo(csession, CURLINFO_RESPONSE_CODE, &rc);
|
||||
@@ -51,11 +59,7 @@ curl_describe(const std::string& path, bool logs) {
|
||||
custom_msg, "Accept: application/x-rtsp-mh, application/rtsl, application/sdp");
|
||||
curl_easy_setopt(csession, CURLOPT_RTSPHEADER, custom_msg);
|
||||
curl_easy_setopt(csession, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_DESCRIBE);
|
||||
curl_easy_setopt(csession, CURLOPT_WRITEDATA, protofile);
|
||||
// This request will check if the given path is right without the need of encrypted ids
|
||||
curl_easy_perform(
|
||||
csession); // will return 404 if no ids and bad route, 401 if ids, 200 is all ok
|
||||
res = curl_easy_getinfo(csession, CURLINFO_RESPONSE_CODE, &rc);
|
||||
unsigned long pos = path.find("@");
|
||||
if (pos != std::string::npos) {
|
||||
std::string encoded = etix::tool::encode::encode64(path.substr(7, pos - 7));
|
||||
@@ -63,20 +67,27 @@ curl_describe(const std::string& path, bool logs) {
|
||||
curl_slist_append(custom_msg, std::string("Authorization: Basic " + encoded).c_str());
|
||||
curl_easy_setopt(csession, CURLOPT_RTSPHEADER, custom_msg);
|
||||
curl_easy_setopt(csession, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_DESCRIBE);
|
||||
curl_easy_setopt(csession, CURLOPT_WRITEDATA, protofile);
|
||||
// curl_easy_setopt(csession, CURLOPT_WRITEDATA, protofile);
|
||||
// This request will check if the given ids are good
|
||||
curl_easy_perform(csession); // will return 404 if good ids, 401 if bad ids
|
||||
res = curl_easy_getinfo(csession, CURLINFO_RESPONSE_CODE, &rc);
|
||||
} else {
|
||||
curl_easy_perform(
|
||||
csession); // will return 404 if no ids and bad route, 401 if ids, 200 is all ok
|
||||
res = curl_easy_getinfo(csession, CURLINFO_RESPONSE_CODE, &rc);
|
||||
}
|
||||
|
||||
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
|
||||
// GST-RTSP-SERVER returns 404 instead of 401, then 401 instead of 404.
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,15 +17,25 @@
|
||||
namespace etix {
|
||||
namespace cameradar {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
// 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,16 +46,16 @@ 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));
|
||||
std::this_thread::sleep_for(30ms);
|
||||
}
|
||||
worker.join();
|
||||
}
|
||||
|
||||
//! This loop is used to add all the tasks specified in the command line
|
||||
//! And then run them successively
|
||||
// This loop is used to add all the tasks specified in the command line
|
||||
// And then run them successively
|
||||
void
|
||||
dispatcher::do_stuff() {
|
||||
if (opts.second.exist("-d")) {
|
||||
@@ -76,9 +86,11 @@ 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;
|
||||
}
|
||||
std::this_thread::sleep_for(30ms);
|
||||
}
|
||||
this->current = task::finished;
|
||||
}
|
||||
|
||||
@@ -88,12 +88,12 @@ create_recursive_folder(const std::string& folder) {
|
||||
|
||||
std::string
|
||||
get_file_folder(std::string full_file_path) {
|
||||
//! remove ending slash
|
||||
// remove ending slash
|
||||
if (full_file_path.back() == '/') full_file_path.pop_back();
|
||||
|
||||
size_t last_slash_position = full_file_path.find_last_of('/');
|
||||
|
||||
//! it there is no slash, there is no folder to return
|
||||
// it there is no slash, there is no folder to return
|
||||
if (last_slash_position == std::string::npos) return "";
|
||||
|
||||
return std::string(full_file_path, 0, last_slash_position);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
namespace etix {
|
||||
namespace cameradar {
|
||||
|
||||
//! Launches a command and checks for the return value
|
||||
// Launches a command and checks for the return value
|
||||
bool
|
||||
launch_command(const std::string& cmd) {
|
||||
int status = system(cmd.c_str());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -24,7 +24,6 @@ void
|
||||
print_version() {
|
||||
std::cout << "Cameradar version " << CAMERADAR_VERSION << std::endl;
|
||||
std::cout << "Build " << CAMERADAR_VERSION_BUILD << std::endl;
|
||||
std::cout << "Git commit " << CAMERADAR_VERSION_GIT_SHA1 << std::endl;
|
||||
}
|
||||
|
||||
// Command line parsing
|
||||
@@ -32,6 +31,8 @@ std::pair<bool, etix::tool::opt_parse>
|
||||
parse_cmdline(int argc, char* argv[]) {
|
||||
auto opt_parse = etix::tool::opt_parse{ argc, argv };
|
||||
|
||||
opt_parse.optional("-s", "Set subnets (e.g.: `172.16.0.0/24`)", true);
|
||||
opt_parse.optional("-p", "Set ports (e.g.: `554,8554`)", true);
|
||||
opt_parse.optional("-c", "Path to the configuration file (-c /path/to/conf)", true);
|
||||
opt_parse.optional("-l", "Set log level (-l 4 will only show warnings and errors)", true);
|
||||
opt_parse.optional("-d", "Launch the discovery tool on the given subnet", false);
|
||||
@@ -86,18 +87,10 @@ main(int argc, char* argv[]) {
|
||||
if (not args.first) return EXIT_FAILURE;
|
||||
|
||||
print_version();
|
||||
// configure file configuration path
|
||||
auto conf_path = std::string{};
|
||||
if (not args.second.exist("-c")) {
|
||||
conf_path = etix::cameradar::default_configuration_path;
|
||||
LOG_WARN_("No custom path set, trying to use default path: " + conf_path, "main");
|
||||
} else {
|
||||
conf_path = args.second["-c"];
|
||||
}
|
||||
|
||||
if (not args.second.exist("-l")) {
|
||||
etix::tool::logger::get_instance("cameradar").set_level(etix::tool::loglevel::INFO);
|
||||
LOG_INFO_("No log level set, using log level 2 (ignoring DEBUG)", "main");
|
||||
etix::tool::logger::get_instance("cameradar").set_level(etix::tool::loglevel::DEBUG);
|
||||
LOG_INFO_("No log level set, using log level 1", "main");
|
||||
} else {
|
||||
try {
|
||||
int level = std::stoi(args.second["-l"]);
|
||||
@@ -110,7 +103,7 @@ main(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
// Try to load the configuration
|
||||
auto conf = cmrdr::load(conf_path);
|
||||
auto conf = cmrdr::load(args);
|
||||
if (not conf.first) { return EXIT_FAILURE; }
|
||||
|
||||
LOG_INFO_("Configuration successfully loaded", "main");
|
||||
@@ -124,7 +117,7 @@ main(int argc, char* argv[]) {
|
||||
auto plug = std::make_shared<etix::cameradar::cache_manager>(conf.second.cache_manager_path,
|
||||
conf.second.cache_manager_name);
|
||||
|
||||
if (not plug->make_instance()) {
|
||||
if (not plug || not plug->make_instance()) {
|
||||
LOG_ERR_(std::string("Invalid cache manager "), "cameradar");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -25,9 +25,9 @@ static const std::string no_ids_warning_ =
|
||||
"default routes. "
|
||||
"Path bruteforce is impossible without the IDs.";
|
||||
|
||||
//! Tries to match the detected combination of Username / Password
|
||||
//! with the camera stream. Creates a resource in the DB upon
|
||||
//! valid discovery
|
||||
// Tries to match the detected combination of Username / Password
|
||||
// with the camera stream. Creates a resource in the DB upon
|
||||
// valid discovery
|
||||
bool
|
||||
brutelogs::test_ids(const etix::cameradar::stream_model& stream,
|
||||
const std::string& password,
|
||||
@@ -36,26 +36,28 @@ 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_INFO_("Testing ids : " + path, "brutelogs");
|
||||
try {
|
||||
if (curl_describe(path, true)) {
|
||||
LOG_DEBUG_("[FOUND IDS] : " + path, "bruteforce");
|
||||
LOG_INFO_("[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
|
||||
};
|
||||
if ((*cache)->has_changed(stream)) return true;
|
||||
(*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 };
|
||||
if ((*cache)->has_changed(stream)) return true;
|
||||
(*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;
|
||||
}
|
||||
@@ -68,19 +70,35 @@ ids_already_found(std::vector<stream_model> streams, stream_model stream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//! Tries to discover the right IDs on all RTSP streams in DB
|
||||
//! Uses the ids.json file to try different combinations
|
||||
bool
|
||||
brutelogs::bruteforce_camera(const stream_model& stream) const {
|
||||
for (const auto& username : conf.usernames) {
|
||||
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
|
||||
break;
|
||||
for (const auto& password : conf.passwords) {
|
||||
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
|
||||
break;
|
||||
if ((*cache)->has_changed(stream)) return true;
|
||||
if (test_ids(stream, password, username)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tries to discover the right IDs on all RTSP streams in DB
|
||||
// Uses the ids.json file to try different combinations
|
||||
bool
|
||||
brutelogs::run() const {
|
||||
std::vector<std::future<bool>> futures;
|
||||
|
||||
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();
|
||||
bool doubleskip;
|
||||
LOG_DEBUG_("Found " + std::to_string(streams.size()) + " streams in the cache", "brutelogs");
|
||||
size_t found = 0;
|
||||
for (const auto& stream : streams) {
|
||||
doubleskip = false;
|
||||
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
|
||||
break;
|
||||
if ((found < streams.size()) && ids_already_found(streams, stream)) {
|
||||
@@ -88,34 +106,23 @@ 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) {
|
||||
if (doubleskip ||
|
||||
signal_handler::instance().should_stop() !=
|
||||
etix::cameradar::stop_priority::running)
|
||||
break;
|
||||
for (const auto& password : conf.passwords) {
|
||||
if (doubleskip ||
|
||||
signal_handler::instance().should_stop() !=
|
||||
etix::cameradar::stop_priority::running)
|
||||
break;
|
||||
if (test_ids(stream, password, username)) {
|
||||
++found;
|
||||
doubleskip = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
futures.push_back(
|
||||
std::async(std::launch::async, &brutelogs::bruteforce_camera, this, stream));
|
||||
}
|
||||
}
|
||||
for (auto& fit : futures) {
|
||||
if (fit.get()) { ++found; }
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ static const std::string no_route_found_ =
|
||||
"default "
|
||||
"routes. Thumbnail generation is impossible without the path.";
|
||||
|
||||
//! Tries to match the detected combination of Username / Password
|
||||
//! with a route for the camera stream. Creates a resource in the DB upon
|
||||
//! valid discovery
|
||||
// Tries to match the detected combination of Username / Password
|
||||
// with a route for the camera stream. Creates a resource in the DB upon
|
||||
// valid discovery
|
||||
bool
|
||||
brutepath::test_path(const stream_model& stream, const std::string& route) const {
|
||||
bool found = false;
|
||||
@@ -29,7 +29,7 @@ brutepath::test_path(const stream_model& stream, const std::string& route) const
|
||||
stream.address + ":" + std::to_string(stream.port);
|
||||
if (route.front() != '/') { path += "/"; }
|
||||
path += route;
|
||||
LOG_DEBUG_("Testing path : " + path, "brutepath");
|
||||
LOG_INFO_("Testing path : " + path, "brutepath");
|
||||
try {
|
||||
if (curl_describe(path, false)) {
|
||||
// insert in DB and go to the next port, print a cool message
|
||||
@@ -40,6 +40,7 @@ brutepath::test_path(const stream_model& stream, const std::string& route) const
|
||||
stream.service_name, stream.product, stream.protocol, stream.state, true,
|
||||
stream.ids_found, stream.thumbnail_path
|
||||
};
|
||||
if ((*cache)->has_changed(stream)) return true;
|
||||
(*cache)->update_stream(newstream);
|
||||
} else {
|
||||
stream_model newstream{
|
||||
@@ -47,6 +48,7 @@ brutepath::test_path(const stream_model& stream, const std::string& route) const
|
||||
stream.service_name, stream.product, stream.protocol, stream.state, false,
|
||||
stream.ids_found, stream.thumbnail_path
|
||||
};
|
||||
if ((*cache)->has_changed(stream)) return true;
|
||||
(*cache)->update_stream(newstream);
|
||||
}
|
||||
} catch (const std::runtime_error& e) { LOG_INFO_(e.what(), "brutepath"); }
|
||||
@@ -62,10 +64,26 @@ path_already_found(std::vector<stream_model> streams, stream_model model) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//! Tries to discover a route on all RTSP streams in DB
|
||||
//! Uses the url.json file to try different routes
|
||||
bool
|
||||
brutepath::bruteforce_camera(const stream_model& stream) const {
|
||||
for (const auto& route : conf.paths) {
|
||||
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
|
||||
break;
|
||||
if ((*cache)->has_changed(stream)) return true;
|
||||
if (test_path(stream, route)) {
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tries to discover a route on all RTSP streams in DB
|
||||
// Uses the url.json file to try different routes
|
||||
bool
|
||||
brutepath::run() const {
|
||||
std::vector<std::future<bool>> futures;
|
||||
|
||||
LOG_INFO_("Beginning bruteforce of the camera paths task, it may take a while.", "bruteforce");
|
||||
std::vector<stream_model> streams = (*cache)->get_streams();
|
||||
int found = 0;
|
||||
@@ -74,22 +92,18 @@ brutepath::run() const {
|
||||
break;
|
||||
if (path_already_found(streams, stream)) {
|
||||
LOG_INFO_(stream.address +
|
||||
" : This camera's path was already discovered in the database."
|
||||
"Skipping to the next camera.",
|
||||
" : This camera's path was already discovered in the database. Skipping "
|
||||
"to the next camera.",
|
||||
"brutepath");
|
||||
++found;
|
||||
} else {
|
||||
for (const auto& route : conf.paths) {
|
||||
if (signal_handler::instance().should_stop() !=
|
||||
etix::cameradar::stop_priority::running)
|
||||
break;
|
||||
if (test_path(stream, route)) {
|
||||
found++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
futures.push_back(
|
||||
std::async(std::launch::async, &brutepath::bruteforce_camera, this, stream));
|
||||
}
|
||||
}
|
||||
for (auto& fit : futures) {
|
||||
if (fit.get()) { ++found; }
|
||||
}
|
||||
if (!found) {
|
||||
LOG_WARN_(no_route_found_, "brutepath");
|
||||
|
||||
|
||||
@@ -17,24 +17,24 @@
|
||||
namespace etix {
|
||||
namespace cameradar {
|
||||
|
||||
//! The first command checks if dpkg finds nmap in the system by cutting the
|
||||
//! result and grepping
|
||||
//! nmap from it.
|
||||
//!
|
||||
//! The second command checks the version of nmap, right now it needs to be the
|
||||
//! 6.47 but this could
|
||||
//! be changed to 6 or greater depending on the needs. In a docker container
|
||||
//! this should not be a
|
||||
//! problem.
|
||||
// The first command checks if dpkg finds nmap in the system by cutting the
|
||||
// result and grepping
|
||||
// nmap from it.
|
||||
//
|
||||
// The second command checks the version of nmap, right now it needs to be the
|
||||
// 6.47 but this could
|
||||
// be changed to 6 or greater depending on the needs. In a docker container
|
||||
// this should not be a
|
||||
// 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
|
||||
//! Uses the subnets specified in the conf file to launch nmap
|
||||
// Launches and checks the return of the nmap command
|
||||
// Uses the subnets specified in the conf file to launch nmap
|
||||
bool
|
||||
mapping::run() const {
|
||||
if (nmap_is_ok()) {
|
||||
@@ -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");
|
||||
|
||||
@@ -24,16 +24,16 @@ static const std::string no_hosts_found_ =
|
||||
"were "
|
||||
"accessible";
|
||||
|
||||
//! Avoids segfaults on unknown xml structure
|
||||
// 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)
|
||||
//! Pushes it back to the data structure
|
||||
// Parse a single host node (generally containing only one camera)
|
||||
// Pushes it back to the data structure
|
||||
void
|
||||
parsing::parse_camera(TiXmlElement* xml_host, std::vector<stream_model>& data) const {
|
||||
TiXmlElement* xml_streams = xml_host->FirstChild("ports")->ToElement();
|
||||
@@ -51,15 +51,15 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
//! Prints all detected cameras into the data structure and stops the program if
|
||||
//! no open RTSP streams were found
|
||||
// Prints all detected cameras into the data structure and stops the program if
|
||||
// no open RTSP streams were found
|
||||
bool
|
||||
parsing::print_detected_cameras(const std::vector<stream_model>& data) const {
|
||||
int added = 0;
|
||||
@@ -90,8 +90,8 @@ parsing::print_detected_cameras(const std::vector<stream_model>& data) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Opens the nmap output file, parses the data of each discovered port
|
||||
//! Adds the RTSP ports only into the DB
|
||||
// Opens the nmap output file, parses the data of each discovered port
|
||||
// Adds the RTSP ports only into the DB
|
||||
bool
|
||||
parsing::run() const {
|
||||
std::vector<stream_model> data;
|
||||
@@ -113,7 +113,7 @@ parsing::run() const {
|
||||
LOG_WARN_(no_hosts_found_, "parsing");
|
||||
if (data.size() == 0) { LOG_WARN_("No cameras were discovered", "parsing"); }
|
||||
return print_detected_cameras(data);
|
||||
} catch (std::exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERR_("Error during parsing. brutepath aborted : " + std::string(e.what()), "parsing");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
namespace etix {
|
||||
namespace cameradar {
|
||||
|
||||
//! Launches and checks the return of the nmap command
|
||||
//! Uses the subnets specified in the conf file to launch nmap
|
||||
// Launches and checks the return of the nmap command
|
||||
// Uses the subnets specified in the conf file to launch nmap
|
||||
bool
|
||||
print::run() const {
|
||||
std::vector<stream_model> results = (*cache)->get_valid_streams();
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
namespace etix {
|
||||
namespace cameradar {
|
||||
|
||||
//! Gets all the discovered streams with good routes and logs
|
||||
//! And launches an ffmpeg command to generate a thumbnail
|
||||
//! In order to check for the stream validity
|
||||
// Gets all the discovered streams with good routes and logs
|
||||
// And launches an ffmpeg command to generate a thumbnail
|
||||
// In order to check for the stream validity
|
||||
bool
|
||||
stream_check::run() const {
|
||||
GstElement* pipeline;
|
||||
@@ -29,18 +29,29 @@ 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_(
|
||||
@@ -56,7 +67,9 @@ stream_check::run() const {
|
||||
(*cache)->update_stream(invalidstream);
|
||||
return false;
|
||||
}
|
||||
LOG_INFO_("[" + stream.address + "] Set pipeline to playing", "stream_check");
|
||||
LOG_INFO_("[" + stream.address +
|
||||
"] This stream is accessible and seems to be functional",
|
||||
"stream_check");
|
||||
}
|
||||
}
|
||||
LOG_INFO_("All streams could be accessed with GStreamer", "stream_check");
|
||||
|
||||
@@ -41,56 +41,74 @@ thumbnail::build_output_file_path(const std::string& path) const {
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
//! Gets all the discovered streams with good routes and logs
|
||||
//! And launches an ffmpeg command to generate a thumbnail
|
||||
//! In order to check for the stream validity
|
||||
bool
|
||||
thumbnail::generate_thumbnail(const stream_model& stream) const {
|
||||
LOG_INFO_("Generating thumbnail for " + stream.address, "thumbnail_generation");
|
||||
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
|
||||
return false;
|
||||
std::string ffmpeg_cmd =
|
||||
"mkdir -p %s ; "
|
||||
"ffmpeg "
|
||||
"-rtsp_transport tcp "
|
||||
"-y "
|
||||
"-nostdin "
|
||||
"-loglevel quiet " // no logs
|
||||
"-i '%s' " // input
|
||||
"-vcodec mjpeg " // jpeg codec
|
||||
"-vframes 1 " // only take one frame
|
||||
"-an " // disable audio
|
||||
"-f image2 " // force image
|
||||
"-s 240x180 " // force size
|
||||
"'%s'";
|
||||
std::string fullpath = make_path(stream);
|
||||
std::string output = build_output_file_path(stream.address);
|
||||
ffmpeg_cmd = tool::fmt(ffmpeg_cmd.c_str(),
|
||||
output.substr(0, output.find_last_of("/")).c_str(),
|
||||
fullpath.c_str(),
|
||||
output.c_str());
|
||||
if (!launch_command(ffmpeg_cmd)) {
|
||||
LOG_WARN_("The following command [" + ffmpeg_cmd +
|
||||
"] didn't work. That can either mean that the stream is "
|
||||
"not valid or "
|
||||
"that there is a problem with the camera.",
|
||||
"thumbnail_generation");
|
||||
return false;
|
||||
} else {
|
||||
LOG_DEBUG_("Generated thumbnail : " + ffmpeg_cmd, "thumbnail_generation");
|
||||
try {
|
||||
stream_model result{ stream.address, stream.port, stream.username,
|
||||
stream.password, stream.route, stream.service_name,
|
||||
stream.product, stream.protocol, stream.state,
|
||||
stream.path_found, stream.ids_found, output };
|
||||
(*cache)->update_stream(result);
|
||||
|
||||
} catch (const std::exception& e) { LOG_DEBUG_(e.what(), "thumbnail_generation"); }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Gets all the discovered streams with good routes and logs
|
||||
// And launches an ffmpeg command to generate a thumbnail
|
||||
// In order to check for the stream validity
|
||||
bool
|
||||
thumbnail::run() const {
|
||||
std::vector<std::future<bool>> futures;
|
||||
std::vector<stream_model> streams = (*cache)->get_valid_streams();
|
||||
LOG_INFO_("Started thumbnail generation, it may take a while", "thumbnail");
|
||||
for (const auto& stream : streams) {
|
||||
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
|
||||
break;
|
||||
std::string ffmpeg_cmd =
|
||||
"mkdir -p %s ; "
|
||||
"ffmpeg "
|
||||
"-y "
|
||||
"-nostdin "
|
||||
"-loglevel quiet "
|
||||
"-i '%s' "
|
||||
"-vcodec mjpeg "
|
||||
"-vframes 1 "
|
||||
"-an "
|
||||
"-f image2 "
|
||||
"-s 320x240 "
|
||||
"'%s'";
|
||||
std::string fullpath = make_path(stream);
|
||||
std::string output = build_output_file_path(stream.address);
|
||||
ffmpeg_cmd = tool::fmt(ffmpeg_cmd.c_str(),
|
||||
output.substr(0, output.find_last_of("/")).c_str(),
|
||||
fullpath.c_str(),
|
||||
output.c_str());
|
||||
if (!launch_command(ffmpeg_cmd)) {
|
||||
LOG_WARN_("The following command [" + ffmpeg_cmd +
|
||||
"] didn't work. That can either mean that the stream is "
|
||||
"not valid or "
|
||||
"that there is a problem with the camera.",
|
||||
"thumbnail_generation");
|
||||
} else {
|
||||
LOG_DEBUG_("Generated thumbnail : " + ffmpeg_cmd, "thumbnail_generation");
|
||||
try {
|
||||
stream_model result{ stream.address, stream.port, stream.username,
|
||||
stream.password, stream.route, stream.service_name,
|
||||
stream.product, stream.protocol, stream.state,
|
||||
stream.path_found, stream.ids_found, output };
|
||||
(*cache)->update_stream(result);
|
||||
|
||||
} catch (std::exception& e) { LOG_DEBUG_(e.what(), "thumbnail_generation"); }
|
||||
}
|
||||
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;
|
||||
}
|
||||
int done = 0;
|
||||
for (const auto& stream : streams) {
|
||||
futures.push_back(
|
||||
std::async(std::launch::async, &thumbnail::generate_thumbnail, this, stream));
|
||||
}
|
||||
for (auto& fit : futures) {
|
||||
if (fit.get()) { ++done; }
|
||||
}
|
||||
LOG_INFO_("All thumbnails have been successfully generated in " +
|
||||
this->conf.thumbnail_storage_path,
|
||||
"thumbnail_generation");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ESC_SEQ="\x1b["
|
||||
COL_RESET=$ESC_SEQ"39;49;00m"
|
||||
COL_RED=$ESC_SEQ"31;01m"
|
||||
COL_GREEN=$ESC_SEQ"32;01m"
|
||||
COL_YELLOW=$ESC_SEQ"33;01m"
|
||||
COL_BLUE=$ESC_SEQ"34;01m"
|
||||
COL_MAGENTA=$ESC_SEQ"35;01m"
|
||||
COL_CYAN=$ESC_SEQ"36;01m"
|
||||
|
||||
echo -e $COL_YELLOW"Creating package ... "$COL_RESET
|
||||
{ cd ..
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
make package
|
||||
cp cameradar_*_Release_Linux.tar.gz ../deployment
|
||||
cd ../deployment
|
||||
} &> /dev/null
|
||||
echo -e $COL_GREEN"OK!"$COL_RESET
|
||||
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:16.04
|
||||
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")
|
||||
|
||||
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,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,55 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type manager struct {
|
||||
Config
|
||||
|
||||
Tests []Result
|
||||
Result *TestCase
|
||||
DB mysql_db
|
||||
}
|
||||
|
||||
// Config needs refacto
|
||||
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,88 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Service needs refacto
|
||||
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,173 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MysqlDB needs refacto
|
||||
type MysqlDB struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
DbName string `json:"db_name"`
|
||||
}
|
||||
|
||||
// CameradarConfig needs refacto
|
||||
type CameradarConfig struct {
|
||||
MysqlDB MysqlDB `json:"mysql_db"`
|
||||
Subnets string `json:"subnets"`
|
||||
Ports string `json:"ports"`
|
||||
RtspURLFile string `json:"rtsp_url_file"`
|
||||
RtspIdsFile string `json:"rtsp_ids_file"`
|
||||
ThumbnailStoragePath string `json:"thumbnail_storage_path"`
|
||||
}
|
||||
|
||||
// Result needs refacto
|
||||
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"`
|
||||
}
|
||||
|
||||
// TestCase needs refacto
|
||||
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
|
||||
}
|
||||
|
||||
// Extend needs refacto
|
||||
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,149 @@
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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 ---")
|
||||
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