Compare commits

...

15 Commits

Author SHA1 Message Date
Brendan LE GLAUNEC 27b296c9d2 v1.0.4 : Fixed nmap package detection 2016-08-31 09:39:08 +02:00
Brendan LE GLAUNEC 006c0139be Merge branch 'master' of github.com:EtixLabs/cameradar 2016-08-30 16:59:08 +02:00
Brendan LE GLAUNEC 63119d3ff3 v1.0.3 : Corrected GStreamer check 2016-08-30 16:58:46 +02:00
Brendan LE GLAUNEC 5859e9c595 Removed forgotten logs 2016-08-26 12:59:46 +02:00
Brendan LE GLAUNEC d0220ceb7f v1.0.2 - Fix issues with MySQL CM 2016-08-24 12:11:54 +02:00
Brendan LE GLAUNEC 064a6ff588 v1.0.1 : Removed useless text from the Readme 2016-07-08 15:05:17 +02:00
Brendan LE GLAUNEC 9a269bfe0e v1.0.1 : Updated to 16.04 & removed boost dependency 2016-07-07 17:47:08 +02:00
Brendan LE GLAUNEC c44b933a83 v 1.0.0 - Changed tag - Updated deployment version 2016-06-21 11:00:35 +02:00
Brendan LE GLAUNEC 1f5e9fc502 v 1.0.0 - Added functionnal testing - Needs Travis integration 2016-06-21 10:53:24 +02:00
Brendan LE GLAUNEC 08231074b9 Update README.md 2016-06-07 12:38:17 +02:00
Brendan LE GLAUNEC c6d801750e Cameradar now waits for MySQL before being deployed 2016-06-03 09:07:07 +02:00
Brendan LE GLAUNEC 2cf49a8db4 Update CMakeLists.txt 2016-06-03 08:49:55 +02:00
Brendan LE GLAUNEC 4fba8a8594 Update README.md 2016-06-02 10:13:48 +02:00
Brendan LE GLAUNEC 76365e3a07 v0.2.2 : Cameradar now supports badly configured cameras 2016-05-27 14:36:52 +02:00
Brendan LE GLAUNEC e6a38af241 Update README.md 2016-05-26 10:52:29 +02:00
42 changed files with 1242 additions and 154 deletions
+8 -5
View File
@@ -19,9 +19,11 @@ set (PROJECT_NAME cameradar)
project (${PROJECT_NAME}) project (${PROJECT_NAME})
set (${PROJECT_NAME}_VERSION_MAJOR 0) message ("Here")
set (${PROJECT_NAME}_VERSION_MINOR 2)
set (${PROJECT_NAME}_VERSION_PATCH 0) set (${PROJECT_NAME}_VERSION_MAJOR 1)
set (${PROJECT_NAME}_VERSION_MINOR 0)
set (${PROJECT_NAME}_VERSION_PATCH 4)
set (${PROJECT_NAME}_SUFFIX "-beta") set (${PROJECT_NAME}_SUFFIX "-beta")
set (${PROJECT_NAME}_VERSION "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}.${${PROJECT_NAME}_VERSION_PATCH}${${PROJECT_NAME}_SUFFIX}") set (${PROJECT_NAME}_VERSION "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}.${${PROJECT_NAME}_VERSION_PATCH}${${PROJECT_NAME}_SUFFIX}")
@@ -103,6 +105,7 @@ set (${CAMERADAR_LIBRARIES} "")
#build cache managers #build cache managers
add_subdirectory (deps) add_subdirectory (deps)
message ("Debug")
add_subdirectory (cameradar_standalone) add_subdirectory (cameradar_standalone)
add_subdirectory (cache_managers) add_subdirectory (cache_managers)
@@ -122,8 +125,8 @@ set (CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}_${${PROJECT_NAME}_VERSION}_${CMAKE
set (CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md") set (CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
set (CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set (CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
set (CPACK_PACKAGE_VERSION_MAJOR "0") set (CPACK_PACKAGE_VERSION_MAJOR "0")
set (CPACK_PACKAGE_VERSION_MINOR "1") set (CPACK_PACKAGE_VERSION_MINOR "2")
set (CPACK_PACKAGE_VERSION_PATCH "0") set (CPACK_PACKAGE_VERSION_PATCH "2")
set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}_${${PROJECT_NAME}_VERSION}") set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}_${${PROJECT_NAME}_VERSION}")
set (CPACK_GENERATOR "TGZ") set (CPACK_GENERATOR "TGZ")
set (CPACK_SOURCE_GENERATOR "TGZ") set (CPACK_SOURCE_GENERATOR "TGZ")
+12 -2
View File
@@ -2,6 +2,10 @@
## An RTSP surveillance camera access multitool ## An RTSP surveillance camera access multitool
[![cameradar License](https://img.shields.io/badge/license-Apache-blue.svg)](#license)
[![Latest release](https://img.shields.io/badge/release-1.0.4-green.svg)](https://github.com/EtixLabs/cameradar/releases/latest)
#### Cameradar allows you to: #### Cameradar allows you to:
* **Detect open RTSP hosts** on any accessible subnetwork * **Detect open RTSP hosts** on any accessible subnetwork
@@ -42,7 +46,7 @@ The quick install uses docker to build Cameradar without polluting your machine
### Dependencies ### Dependencies
The only dependencies are `docker` and `docker-compose`. The only dependencies are `docker`, `docker-tools`, `git` and `make`.
### Five steps guide ### Five steps guide
@@ -63,6 +67,11 @@ The manual installation is recommended if you want to tweak Cameradar and quickl
To install Cameradar you will need these packages To install Cameradar you will need these packages
* cmake (`cmake`) * cmake (`cmake`)
* git (`git`)
* gstreamer1.x (`libgstreamer1.0-dev`)
* ffmpeg (`ffmpeg`)
* boost (`libboost-all-dev`)
* libcurl (`libcurl4-openssl-dev`)
### Steps ### Steps
@@ -119,7 +128,8 @@ The subnetworks should be passed separated by commas only, and their subnet form
"subnets" : "172.100.16.0/24,172.100.17.0/24,localhost,192.168.1.13" "subnets" : "172.100.16.0/24,172.100.17.0/24,localhost,192.168.1.13"
``` ```
The **RTSP ports for most cameras are 554**, so you should probably specify 554 as one of the ports you scan. Not giving any ports in the configuration will scan every port of every host found on the subnetworks..How is formatted Cameradar's result The **RTSP ports for most cameras are 554**, so you should probably specify 554 as one of the ports you scan. Not giving any ports in the configuration will scan every port of every host found on the subnetworks.
You **can use your own files for the ids and routes dictionaries** used to bruteforce the cameras, but the Cameradar repo already gives you a good base that works with most cameras. You **can use your own files for the ids and routes dictionaries** used to bruteforce the cameras, but the Cameradar repo already gives you a good base that works with most cameras.
The thumbnail storage path should be a **valid and accessible directory** in which the thumbnails will be stored. The thumbnail storage path should be a **valid and accessible directory** in which the thumbnails will be stored.
@@ -53,8 +53,10 @@ dumb_cache_manager::set_streams(std::vector<etix::cameradar::stream_model> model
//! Inserts a single stream to the cache //! Inserts a single stream to the cache
void void
dumb_cache_manager::update_stream(const etix::cameradar::stream_model& newmodel) { dumb_cache_manager::update_stream(const etix::cameradar::stream_model& newmodel) {
for (auto& it : this->streams) { for (auto& stream : this->streams) {
if (it.address == newmodel.address && it.port == newmodel.port) { it = newmodel; } if (stream.address == newmodel.address && stream.port == newmodel.port) {
stream = newmodel;
}
} }
} }
@@ -62,8 +64,9 @@ dumb_cache_manager::update_stream(const etix::cameradar::stream_model& newmodel)
std::vector<etix::cameradar::stream_model> std::vector<etix::cameradar::stream_model>
dumb_cache_manager::get_streams() { dumb_cache_manager::get_streams() {
std::vector<stream_model> ret; std::vector<stream_model> ret;
for (const auto& it : this->streams) { for (const auto& stream : this->streams) {
if (not it.service_name.compare("rtsp") && not it.state.compare("open")) ret.push_back(it); if (not stream.service_name.compare("rtsp") && not stream.state.compare("open"))
ret.push_back(stream);
} }
return ret; return ret;
} }
@@ -72,10 +75,10 @@ dumb_cache_manager::get_streams() {
std::vector<etix::cameradar::stream_model> std::vector<etix::cameradar::stream_model>
dumb_cache_manager::get_valid_streams() { dumb_cache_manager::get_valid_streams() {
std::vector<stream_model> ret; std::vector<stream_model> ret;
for (const auto& it : this->streams) { for (const auto& stream : this->streams) {
if ((not it.service_name.compare("rtsp") && not it.state.compare("open")) && it.ids_found && if ((not stream.service_name.compare("rtsp") && not stream.state.compare("open")) &&
it.path_found) stream.ids_found && stream.path_found)
ret.push_back(it); ret.push_back(stream);
} }
return ret; return ret;
} }
@@ -149,28 +149,33 @@ void
mysql_cache_manager::set_streams(std::vector<etix::cameradar::stream_model> models) { mysql_cache_manager::set_streams(std::vector<etix::cameradar::stream_model> models) {
LOG_DEBUG_("Beginning stream list DB insertion", "mysql_cache_manager"); LOG_DEBUG_("Beginning stream list DB insertion", "mysql_cache_manager");
for (const auto& model : models) { for (const auto& model : models) {
auto query = tool::fmt( if (!model.service_name.compare("rtsp") && !model.state.compare("open")) {
this->exist_query, this->connection.get_db_name().c_str(), model.address.c_str()); auto query = tool::fmt(
auto result = this->connection.query(query); this->exist_query, this->connection.get_db_name().c_str(), model.address.c_str());
// If an entry already exists for this address in the database, auto result = this->connection.query(query);
// no need to insert it. // If an entry already exists for this address in the database,
if (result.data->next()) return; // no need to insert it.
query = tool::fmt(this->insert_with_id_query, // TODO : Update an entry if it already exists.
this->connection.get_db_name().c_str(),
model.address.c_str(), if (result.data->next()) return;
model.password.c_str(),
model.product.c_str(), query = tool::fmt(this->insert_with_id_query,
model.protocol.c_str(), this->connection.get_db_name().c_str(),
model.route.c_str(), model.address.c_str(),
model.service_name.c_str(), model.password.c_str(),
model.state.c_str(), model.product.c_str(),
model.thumbnail_path.c_str(), model.protocol.c_str(),
model.username.c_str(), model.route.c_str(),
std::to_string(model.port).c_str(), model.service_name.c_str(),
std::to_string(model.ids_found).c_str(), model.state.c_str(),
std::to_string(model.path_found).c_str()); model.thumbnail_path.c_str(),
execute_query(query); 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);
}
} }
} }
@@ -21,5 +21,5 @@
// if you're not familiar with Docker, only change the // if you're not familiar with Docker, only change the
// cache_manager_name value // cache_manager_name value
"cache_manager_path" : "../cache_managers", "cache_manager_path" : "../cache_managers",
"cache_manager_name" : "mysql" "cache_manager_name" : "dumb"
} }
+1 -1
View File
@@ -64,7 +64,7 @@ public:
const std::pair<bool, etix::tool::opt_parse>& opts) const std::pair<bool, etix::tool::opt_parse>& opts)
: busy(false) : busy(false)
, current(task::init) , 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())) + std::chrono::system_clock::now())) +
".xml") ".xml")
, conf(conf) , conf(conf)
+6 -3
View File
@@ -71,12 +71,15 @@ curl_describe(const std::string& path, bool logs) {
curl_easy_cleanup(csession); curl_easy_cleanup(csession);
fclose(protofile); fclose(protofile);
curl_global_cleanup(); curl_global_cleanup();
LOG_DEBUG_("Response code : " + std::to_string(rc), "describe");
if (logs) { if (logs) {
if (rc != 401 && pos == std::string::npos) // Some cameras return 400 instead of 401, don't know why.
// Some cameras timeout and then curl considers the status as 0
if (rc != 401 && rc != 400 && rc && pos == std::string::npos)
LOG_INFO_("Unprotected camera discovered.", "brutelogs"); 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);
} }
} }
} }
+8 -7
View File
@@ -42,16 +42,16 @@ brutelogs::test_ids(const etix::cameradar::stream_model& stream,
LOG_DEBUG_("[FOUND IDS] : " + path, "brutelogs"); LOG_DEBUG_("[FOUND IDS] : " + path, "brutelogs");
found = true; found = true;
stream_model newstream{ stream_model newstream{
stream.address, stream.port, username, password, stream.address, stream.port, username, password,
stream.route, stream.service_name, stream.product, stream.protocol, stream.route, stream.service_name, stream.product, stream.protocol,
stream.state, stream.path_found, true, stream.thumbnail_path stream.state, true, stream.path_found, stream.thumbnail_path
}; };
(*cache)->update_stream(newstream); (*cache)->update_stream(newstream);
} else { } else {
stream_model newstream{ stream.address, stream.port, username, stream_model newstream{ stream.address, stream.port, username,
password, stream.route, stream.service_name, password, stream.route, stream.service_name,
stream.product, stream.protocol, stream.state, stream.product, stream.protocol, stream.state,
stream.path_found, false, stream.thumbnail_path }; false, stream.path_found, stream.thumbnail_path };
(*cache)->update_stream(newstream); (*cache)->update_stream(newstream);
} }
} catch (const std::runtime_error& e) { } catch (const std::runtime_error& e) {
@@ -77,6 +77,7 @@ brutelogs::run() const {
"take a while.", "take a while.",
"brutelogs"); "brutelogs");
std::vector<etix::cameradar::stream_model> streams = (*cache)->get_streams(); std::vector<etix::cameradar::stream_model> streams = (*cache)->get_streams();
LOG_DEBUG_("Found " + std::to_string(streams.size()) + " streams in the cache", "brutelogs");
bool doubleskip; bool doubleskip;
size_t found = 0; size_t found = 0;
for (const auto& stream : streams) { for (const auto& stream : streams) {
+2 -5
View File
@@ -29,11 +29,8 @@ namespace cameradar {
bool bool
nmap_is_ok() { nmap_is_ok() {
return ( return (
launch_command("test `dpkg -l | cut -c 5-9 | grep nmap` = nmap") (system("dpkg -l | cut -c 5-9 | grep nmap") == 0)
// && launch_command("test `nmap --version | cut -c 14-18 | head -n2 | tail -n1` = 6.47") && launch_command("mkdir -p /tmp/scans")); // Creates the directory in which the scans will be stored
&&
launch_command(
"mkdir -p scans")); // Creates the directory in which the scans will be stored
} }
//! Launches and checks the return of the nmap command //! Launches and checks the return of the nmap command
+4 -4
View File
@@ -27,9 +27,9 @@ static const std::string no_hosts_found_ =
//! Avoids segfaults on unknown xml structure //! Avoids segfaults on unknown xml structure
std::string std::string
xml_safe_get(const TiXmlElement* elem, const std::string& attr) { 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())); 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) //! Parse a single host node (generally containing only one camera)
@@ -51,8 +51,8 @@ parsing::parse_camera(TiXmlElement* xml_host, std::vector<stream_model>& data) c
stream.service_name = xml_safe_get(service, "name"); stream.service_name = xml_safe_get(service, "name");
stream.product = xml_safe_get(service, "product"); stream.product = xml_safe_get(service, "product");
} else { } else {
stream.service_name = "Closed"; stream.service_name = "closed";
stream.product = "Closed"; stream.product = "closed";
} }
data.push_back(stream); data.push_back(stream);
} }
@@ -29,18 +29,25 @@ stream_check::run() const {
std::vector<stream_model> streams = (*cache)->get_valid_streams(); 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) { for (const auto& stream : streams) {
GError* error = NULL; GError* error = NULL;
pipeline = pipeline =
gst_parse_launch("rtspsrc name=source ! rtph264depay ! h264parse ! fakesink", &error); 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) { if (pipeline == NULL) {
LOG_ERR_("[" + stream.address + "] Can't configure pipeline", "stream_check"); LOG_ERR_("[" + stream.address + "] Can't configure pipeline", "stream_check");
return false; return false;
} else { } else {
elem = gst_bin_get_by_name(GST_BIN(pipeline), "source"); 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) { if (gst_element_set_state(pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
LOG_ERR_( LOG_ERR_(
@@ -48,7 +48,12 @@ bool
thumbnail::run() const { thumbnail::run() const {
std::vector<stream_model> streams = (*cache)->get_valid_streams(); std::vector<stream_model> streams = (*cache)->get_valid_streams();
LOG_INFO_("Started thumbnail generation, it may take a while", "thumbnail"); LOG_INFO_("Started thumbnail generation, it may take a while", "thumbnail");
if (not streams.size()) {
LOG_WARN_("There were no valid streams to generate thumbnails from. Cameradar will stop.", "thumbnail_generation");
return false;
}
for (const auto& stream : streams) { for (const auto& stream : streams) {
LOG_DEBUG_("Generating thumbnail for " + stream.address, "thumbnail_generation");
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running) if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
break; break;
std::string ffmpeg_cmd = std::string ffmpeg_cmd =
-91
View File
@@ -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)
+3 -2
View File
@@ -1,4 +1,4 @@
FROM ubuntu:15.10 FROM ubuntu:16.04
MAINTAINER brendan.leglaunec@etixgroup.com MAINTAINER brendan.leglaunec@etixgroup.com
@@ -7,11 +7,12 @@ ENV LD_LIBRARY_PATH="/cameradar/libraries"
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
nmap \ nmap \
ffmpeg \ ffmpeg \
libboost-all-dev \
libgstreamer1.0-dev \ libgstreamer1.0-dev \
gstreamer1.0-plugins-base \ gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \ gstreamer1.0-plugins-good \
libcurl4-openssl-dev \ libcurl4-openssl-dev \
libmysqlclient18 \ libmysqlclient20 \
mysql-client mysql-client
ADD cameradar_*_Release_Linux.tar.gz / ADD cameradar_*_Release_Linux.tar.gz /
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -6,8 +6,8 @@ cameradar:
- "./conf:/tmp/conf:ro" - "./conf:/tmp/conf:ro"
- "./cameradar_thumbnails:/tmp/cameradar_thumbnails" - "./cameradar_thumbnails:/tmp/cameradar_thumbnails"
links: links:
- ext_cctv_mysql - mysql
ext_cctv_mysql: mysql:
image: mysql:5.7 image: mysql:5.7
environment: environment:
MYSQL_ROOT_PASSWORD: root MYSQL_ROOT_PASSWORD: root
+7 -1
View File
@@ -27,12 +27,18 @@ echo -e $COL_GREEN"ok"$COL_RESET
# container. The container has to be linked in docker-compose.yml for cameradar # container. The container has to be linked in docker-compose.yml for cameradar
# to be able to interact with it. # to be able to interact with it.
echo -n "replacing mysql host and port in configuration " echo -n "replacing mysql host and port in configuration "
sed -i s#__MYSQL_ADDR__#ext_cctv_mysql#g $CONF sed -i s#__MYSQL_ADDR__#mysql#g $CONF
# Reaplce 3306 with the port of your DB # Reaplce 3306 with the port of your DB
sed -i s#__MYSQL_PORT__#3306#g $CONF sed -i s#__MYSQL_PORT__#3306#g $CONF
echo -e $COL_GREEN"ok"$COL_RESET 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/bin/cameradar -l 1 -c /conf/cameradar.conf.json &
cameradar_pid=$! cameradar_pid=$!
+43
View File
@@ -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"]
+14
View File
@@ -0,0 +1,14 @@
FROM ubuntu:15.10
MAINTAINER brendan.leglaunec@etixgroup.com
RUN useradd -m vlc; \
apt-get update; \
apt-get install -y vlc-nox
RUN sed -i s/geteuid/getppid/g /usr/bin/vlc
ADD ./docker/screen.png /vlc/screen.png
COPY ./docker/run_vlc.sh /start.sh
COPY ./etix_rtsp_server /etix_rtsp_server
EXPOSE 8554
+14
View File
@@ -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"
}
+44
View File
@@ -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
}
]
}
+31
View File
@@ -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"
]
}
+77
View File
@@ -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"
]
}
+18
View File
@@ -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__
}
+31
View File
@@ -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"
]
}
+77
View File
@@ -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"
]
}
+107
View File
@@ -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
+15
View File
@@ -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/
+16
View File
@@ -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

BIN
View File
Binary file not shown.
Executable
+58
View File
@@ -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
+32
View File
@@ -0,0 +1,32 @@
package main
import (
"encoding/json"
"fmt"
"os"
)
func (m *manager) parseConfig() bool {
// Get config file path
confPath := "conf/cameratest.conf.json"
av := len(os.Args)
if av == 2 {
confPath = os.Args[1]
}
// Load config
fmt.Printf("Loading config file: %s ... ", confPath)
configFile, err := os.Open(confPath)
if err != nil {
fmt.Printf("\nCan't open config file: %s\n", err)
return false
}
dec := json.NewDecoder(configFile)
if err = dec.Decode(&m); err != nil {
fmt.Printf("\nUnable to deserialize config file: %s\n", err)
return false
}
fmt.Println("Configuration file successfully loaded\n")
return true
}
+24
View File
@@ -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
}
+24
View File
@@ -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
}
+28
View File
@@ -0,0 +1,28 @@
package main
import (
"bufio"
"fmt"
"io"
)
// Launch it via goroutine
// Start read log of service
func readLog(service *Service, reader io.ReadCloser) {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
str := scanner.Text()
if service.Console {
fmt.Printf("[%s] %s\n", service.Path, str)
}
fmt.Printf("%s\n", str)
service.Mutex.Lock()
service.Logs = append(service.Logs, str)
service.Mutex.Unlock()
}
if err := scanner.Err(); err != nil {
fmt.Printf("[%s] Service failed: %s\n", service.Path, err)
}
fmt.Printf("Logger of service: [%s] stopped\n", service.Path)
service.Active = false
}
+30
View File
@@ -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 ---")
}
+54
View File
@@ -0,0 +1,54 @@
package main
import (
"fmt"
"sync"
)
type manager struct {
Config
Tests []Result
Result *TestCase
DB mysql_db
}
type Config struct {
Cameradar Service `json:"Cameradar"`
Output string
}
func (m *manager) Init() bool {
fmt.Println("- Parsing")
if !m.parseConfig() {
return false
}
fmt.Println("- Cleaning content")
killService(&m.Config.Cameradar)
return true
}
func (m *manager) Run() bool {
var wg sync.WaitGroup
fmt.Println("\n- Launching all tests")
var newTest = new(TestCase)
newTest.expected = m.Tests
if (m.generateConfig(m.Tests, &m.DB)) {
m.dropDB()
wg.Add(1)
go m.invokeTestCase(newTest, &wg)
m.Result = newTest
}
wg.Wait()
fmt.Printf("All tests completed\n")
return true
}
func (m *manager) Stop() bool {
killService(&m.Config.Cameradar)
return true
}
+87
View File
@@ -0,0 +1,87 @@
package main
import (
"fmt"
"os/exec"
"strings"
"sync"
)
type Service struct {
Path string `json:"Path"`
Args string `json:"Args"`
Ports string `json:"Ports"`
IdsPath string `json:"IdsPath"`
RoutesPath string `json:"RoutesPath"`
DbHost string `json:"dbHost"`
DbPort int `json:"dbPort"`
DbUser string `json:"dbUser"`
DbPassword string `json:"dbPassword"`
DbName string `json:"dbName"`
ThumbPath string `json:"ThumbPath"`
Console bool `json:"Console"`
Logs []string
Active bool // Based on io.ReadCloser status
Mutex sync.Mutex
cmd *exec.Cmd // Go handler of the service
}
func startService(service *Service) bool {
// Launch service
args := strings.Fields(service.Args)
service.cmd = exec.Command(service.Path, args...)
handler, err := service.cmd.StdoutPipe()
if err != nil {
fmt.Println(err)
return false
}
errHandler, err := service.cmd.StderrPipe()
if err != nil {
fmt.Println(err)
return false
}
// Launch
err = service.cmd.Start()
if err != nil {
fmt.Println(err)
return false
}
fmt.Printf("Service: [%s] started\n", service.Path)
service.Active = true
// Read service logs and update service status
// Set pipes
go readLog(service, handler)
go readLog(service, errHandler)
return true
}
// Stop only specified service instance
func stopService(service *Service) {
service.cmd.Process.Kill()
}
// Kill all instances of specified service
func killService(service *Service) {
// Sending SIGTERM
fmt.Printf("Executing: killall %s\n", service.Path)
cmd := exec.Command("killall", service.Path)
err := cmd.Run()
if err != nil {
fmt.Println(err)
}
// Sending SIGABORT, more reliable for VLC
sigAbort := []string{service.Path, "-s", "SIGABRT"}
fmt.Printf("Executing: killall %s -s SIGABRT\n", service.Path)
cmd = exec.Command("killall", sigAbort...)
err = cmd.Run()
if err != nil {
fmt.Println(err)
}
}
+168
View File
@@ -0,0 +1,168 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"sync"
"time"
"net"
)
type mysql_db struct {
Host string `json:"host"`
Port int `json:"port"`
User string `json:"user"`
Password string `json:"password"`
Db_name string `json:"db_name"`
}
type CameradarConfig struct {
Mysql_db mysql_db `json:"mysql_db"`
Subnets string `json:"subnets"`
Ports string `json:"ports"`
Rtsp_url_file string `json:"rtsp_url_file"`
Rtsp_ids_file string `json:"rtsp_ids_file"`
Thumbnail_storage_path string `json:"thumbnail_storage_path"`
}
type Result struct {
Address string `json:"address"`
Password string `json:"password"`
Port string `json:"port"`
Route string `json:"route"`
Username string `json:"username"`
Valid bool `json:"valid,omitempty"`
Thumb string `json:"thumbnail_path,omitempty"`
}
type TestCase struct {
expected []Result
result []Result
time time.Duration
ok bool
}
// Invoke the test
// Wrap results in a TestResult object
func (m *manager) invokeTestCase(testCase *TestCase, wg *sync.WaitGroup) {
startTime := time.Now()
if m.runTestCase(testCase) {
testCase.time = time.Since(startTime)
testCase.ok = true
fmt.Printf("Test OK in %.6fs\n", testCase.time.Seconds())
} else {
testCase.time = time.Since(startTime)
testCase.ok = false
fmt.Printf("Test failed in %.6fs\n", testCase.time.Seconds())
}
wg.Done()
}
func (m *manager) runTestCase(test *TestCase) bool {
fmt.Printf("Test triggered\n")
Cameradar := m.Config.Cameradar
startService(&Cameradar)
for Cameradar.Active {
time.Sleep(5 * time.Millisecond)
}
found := 0
toFind := len(test.expected)
var validResults []Result
if getResult(&test.result, "result.json") {
// Check all valid resutls that are supposed to match
// Add them to the valid results and leave the failed
// ones in the expected slice
for _, r := range test.result {
index := 0
r.Valid = true
for _, e := range test.expected {
e.Thumb = r.Thumb
var err error
var addr[] string
addr, err = net.LookupHost(e.Address)
e.Address = addr[0]
if e == r {
_, err = os.Stat(r.Thumb)
if err == nil {
fmt.Println("The result of ", r.Address, " is valid and the thumbnails were generated by Cameradar.")
found++
validResults = Extend(validResults, r)
test.expected = append(test.expected[:index], test.expected[index+1:]...)
break
} else {
fmt.Println("The result of ", r.Address, " seemed valid, but the thumbnails could not be generated by Cameradar.")
}
}
index++
}
}
index := 0
// If the result did not match the expected but it was supposed to fail
// Add it to the valid results and remove it from the expected slice
for _, e := range test.expected {
if !e.Valid {
found++
validResults = Extend(validResults, e)
test.expected = append(test.expected[:index], test.expected[index+1:]...)
break
}
index++
}
// If we found all the expected results, return true
if found == toFind {
return true
}
test.result = validResults
}
return false
}
func (m *manager) generateConfig(test []Result, DataBase *mysql_db) bool {
var config CameradarConfig
var db mysql_db
db.Host = m.Config.Cameradar.DbHost
db.Port = m.Config.Cameradar.DbPort
db.User = m.Config.Cameradar.DbUser
db.Password = m.Config.Cameradar.DbPassword
db.Db_name = m.Config.Cameradar.DbName
for _, t := range test {
if len(config.Subnets) > 0 {
config.Subnets += ","
}
config.Subnets += t.Address
}
config.Mysql_db = db
config.Ports = m.Config.Cameradar.Ports
config.Rtsp_url_file = m.Config.Cameradar.RoutesPath
config.Rtsp_ids_file = m.Config.Cameradar.IdsPath
config.Thumbnail_storage_path = m.Config.Cameradar.ThumbPath
b, _ := json.Marshal(config)
fmt.Println(string(b))
err := ioutil.WriteFile("tmp_config", b, 0644)
if err != nil {
fmt.Println(err)
return false
}
*DataBase = db
return true
}
func Extend(slice []Result, element Result) []Result {
n := len(slice)
if n == cap(slice) {
// Slice is full; must grow.
// We double its size and add 1, so if the size is zero we still grow.
newSlice := make([]Result, len(slice), 2*len(slice)+1)
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0 : n+1]
slice[n] = element
return slice
}
+146
View File
@@ -0,0 +1,146 @@
package main
import (
"encoding/xml"
"fmt"
"io"
"os"
"time"
)
////////////////////////////////////////////////
// Data declarations
// JUnitTestSuites is a collection of JUnit test suites.
type JUnitTestSuites struct {
XMLName xml.Name `xml:"testsuites"`
Suites []JUnitTestSuite
}
// JUnitTestSuite is a single JUnit test suite which may contain many
// testcases.
type JUnitTestSuite struct {
Tests int `xml:"tests,attr"`
Failures int `xml:"failures,attr"`
Time string `xml:"time,attr"`
TestCases []JUnitTestCase
}
// JUnitTestCase is a single test case with its result.
type JUnitTestCase struct {
Message string `xml:"message,attr"`
Time string `xml:"time,attr"`
Failure *JUnitFailure `xml:"failure,omitempty"`
}
// JUnitFailure contains data related to a failed test.
type JUnitFailure struct {
Message string `xml:"message,attr"`
Type string `xml:"type,attr"`
Contents string `xml:",chardata"`
}
func (m *manager) WriteResults(result TestCase, output string) bool {
fmt.Printf("Displaying results...\n")
// Write Console report
m.writeConsoleReport(result)
// Write XML report
// Open xml
file, err := os.OpenFile(output, os.O_RDONLY|os.O_CREATE, 0644)
if err != nil {
fmt.Printf("Error opening XML: %s\n", err)
return false
}
defer file.Close()
err = m.writeJUnitReportXML(result, file, output)
if err != nil {
fmt.Printf("Error writing XML: %s\n", err)
return false
}
fmt.Printf("-> JUnit XML report written: %s\n", output)
return true
}
// Write tests results under JUnit format on w
func (m *manager) writeJUnitReportXML(result TestCase, r io.ReadWriter, output string) error {
suites := JUnitTestSuites{}
dec := xml.NewDecoder(r)
if err := dec.Decode(&suites); err != nil {
fmt.Printf("\nUnable to deserialize XML log file: %s\n", err)
}
ts := JUnitTestSuite{
Tests: len(result.result) + len(result.expected),
Failures: 0,
Time: fmt.Sprintf("%.6f", result.time.Seconds()),
TestCases: []JUnitTestCase{},
}
// Run throught all iterations
testCase := JUnitTestCase{
Time: fmt.Sprintf("%.6f", result.time.Seconds()),
Failure: nil,
}
if len(result.result) > 0 {
testCase.Message = "These streams matched what we expected:"
}
for _, success := range result.result {
testCase.Message += " " + success.Address
}
if !result.ok {
testCase.Failure = &JUnitFailure{
Message: "These streams did not match what we expected:",
Type: "",
}
}
for _, fail := range result.expected {
ts.Failures++
testCase.Failure.Message += " " + fail.Address
}
ts.TestCases = append(ts.TestCases, testCase)
suites.Suites = append(suites.Suites, ts)
// Fix indent
bytes, err := xml.MarshalIndent(suites, "", "\t")
if err != nil {
return err
}
// Write in param stream
w, err := os.OpenFile(output, os.O_WRONLY|os.O_TRUNC, 0644)
writer := io.Writer(w)
writer.Write(bytes)
return nil
}
func (m *manager) writeConsoleReport(result TestCase) bool {
min := 50 * time.Hour
max := 0 * time.Second
total := 0 * time.Second
successCount := 0
failureCount := 0
if result.ok {
successCount++
total += result.time
if result.time < min {
min = result.time
}
if result.time > max {
max = result.time
}
} else {
failureCount++
}
fmt.Println("--- Test summary ---\n")
if successCount > 0 {
fmt.Printf("Results: %d/%d (%d%%)\n", successCount, successCount+failureCount, successCount*100/(successCount+failureCount))
fmt.Printf("Total time: %.6fs\n", total.Seconds())
fmt.Printf("Average time: %.6fs\n", total.Seconds()/float64(successCount))
fmt.Printf("Min time: %.6fs\n", min.Seconds())
fmt.Printf("Max time: %.6fs\n", max.Seconds())
} else {
fmt.Printf("No test in success\n")
}
return true
}