Merge branch 'zma_to_thread' of github.com:connortechnology/ZoneMinder into zma_to_thread
This commit is contained in:
commit
d2f852b9db
|
@ -168,6 +168,8 @@ set(ZM_DIR_SOUNDS "sounds" CACHE PATH
|
||||||
"Location to look for optional sound files, default: sounds")
|
"Location to look for optional sound files, default: sounds")
|
||||||
set(ZM_PATH_ZMS "/cgi-bin/nph-zms" CACHE PATH
|
set(ZM_PATH_ZMS "/cgi-bin/nph-zms" CACHE PATH
|
||||||
"Web url to zms streaming server, default: /cgi-bin/nph-zms")
|
"Web url to zms streaming server, default: /cgi-bin/nph-zms")
|
||||||
|
set(ZM_PATH_SHUTDOWN "/sbin/shutdown" CACHE PATH
|
||||||
|
"Path to shutdown binary, default: /sbin/shutdown")
|
||||||
|
|
||||||
# Advanced
|
# Advanced
|
||||||
set(ZM_PATH_MAP "/dev/shm" CACHE PATH
|
set(ZM_PATH_MAP "/dev/shm" CACHE PATH
|
||||||
|
@ -872,6 +874,13 @@ include(Pod2Man)
|
||||||
ADD_MANPAGE_TARGET()
|
ADD_MANPAGE_TARGET()
|
||||||
|
|
||||||
# Process subdirectories
|
# Process subdirectories
|
||||||
|
|
||||||
|
# build a bcrypt static library
|
||||||
|
set(BUILD_SHARED_LIBS_SAVED "${BUILD_SHARED_LIBS}")
|
||||||
|
set(BUILD_SHARED_LIBS OFF)
|
||||||
|
add_subdirectory(src/libbcrypt EXCLUDE_FROM_ALL)
|
||||||
|
set(BUILD_SHARED_LIBS "${BUILD_SHARED_LIBS_SAVED}")
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
add_subdirectory(scripts)
|
add_subdirectory(scripts)
|
||||||
add_subdirectory(db)
|
add_subdirectory(db)
|
||||||
|
|
|
@ -186,6 +186,7 @@ CREATE TABLE `Events` (
|
||||||
`Id` bigint unsigned NOT NULL auto_increment,
|
`Id` bigint unsigned NOT NULL auto_increment,
|
||||||
`MonitorId` int(10) unsigned NOT NULL default '0',
|
`MonitorId` int(10) unsigned NOT NULL default '0',
|
||||||
`StorageId` smallint(5) unsigned default 0,
|
`StorageId` smallint(5) unsigned default 0,
|
||||||
|
`SecondaryStorageId` smallint(5) unsigned default 0,
|
||||||
`Name` varchar(64) NOT NULL default '',
|
`Name` varchar(64) NOT NULL default '',
|
||||||
`Cause` varchar(32) NOT NULL default '',
|
`Cause` varchar(32) NOT NULL default '',
|
||||||
`StartTime` datetime default NULL,
|
`StartTime` datetime default NULL,
|
||||||
|
@ -351,6 +352,7 @@ CREATE INDEX `Groups_Monitors_MonitorId_idx` ON `Groups_Monitors` (`MonitorId`);
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `Logs`;
|
DROP TABLE IF EXISTS `Logs`;
|
||||||
CREATE TABLE `Logs` (
|
CREATE TABLE `Logs` (
|
||||||
|
`Id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`TimeKey` decimal(16,6) NOT NULL,
|
`TimeKey` decimal(16,6) NOT NULL,
|
||||||
`Component` varchar(32) NOT NULL,
|
`Component` varchar(32) NOT NULL,
|
||||||
`ServerId` int(10) unsigned,
|
`ServerId` int(10) unsigned,
|
||||||
|
@ -360,6 +362,7 @@ CREATE TABLE `Logs` (
|
||||||
`Message` text NOT NULL,
|
`Message` text NOT NULL,
|
||||||
`File` varchar(255) DEFAULT NULL,
|
`File` varchar(255) DEFAULT NULL,
|
||||||
`Line` smallint(5) unsigned DEFAULT NULL,
|
`Line` smallint(5) unsigned DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`Id`),
|
||||||
KEY `TimeKey` (`TimeKey`)
|
KEY `TimeKey` (`TimeKey`)
|
||||||
) ENGINE=@ZM_MYSQL_ENGINE@;
|
) ENGINE=@ZM_MYSQL_ENGINE@;
|
||||||
|
|
||||||
|
@ -411,7 +414,7 @@ CREATE TABLE `MonitorPresets` (
|
||||||
`Width` smallint(5) unsigned default NULL,
|
`Width` smallint(5) unsigned default NULL,
|
||||||
`Height` smallint(5) unsigned default NULL,
|
`Height` smallint(5) unsigned default NULL,
|
||||||
`Palette` int(10) unsigned default NULL,
|
`Palette` int(10) unsigned default NULL,
|
||||||
`MaxFPS` decimal(5,2) default NULL,
|
`MaxFPS` decimal(5,3) default NULL,
|
||||||
`Controllable` tinyint(3) unsigned NOT NULL default '0',
|
`Controllable` tinyint(3) unsigned NOT NULL default '0',
|
||||||
`ControlId` varchar(16) default NULL,
|
`ControlId` varchar(16) default NULL,
|
||||||
`ControlDevice` varchar(255) default NULL,
|
`ControlDevice` varchar(255) default NULL,
|
||||||
|
@ -456,6 +459,8 @@ CREATE TABLE `Monitors` (
|
||||||
`Palette` int(10) unsigned NOT NULL default '0',
|
`Palette` int(10) unsigned NOT NULL default '0',
|
||||||
`Orientation` enum('0','90','180','270','hori','vert') NOT NULL default '0',
|
`Orientation` enum('0','90','180','270','hori','vert') NOT NULL default '0',
|
||||||
`Deinterlacing` int(10) unsigned NOT NULL default '0',
|
`Deinterlacing` int(10) unsigned NOT NULL default '0',
|
||||||
|
`DecoderHWAccelName` varchar(64),
|
||||||
|
`DecoderHWAccelDevice` varchar(255),
|
||||||
`SaveJPEGs` TINYINT NOT NULL DEFAULT '3' ,
|
`SaveJPEGs` TINYINT NOT NULL DEFAULT '3' ,
|
||||||
`VideoWriter` TINYINT NOT NULL DEFAULT '0',
|
`VideoWriter` TINYINT NOT NULL DEFAULT '0',
|
||||||
`OutputCodec` int(10) unsigned NOT NULL default 0,
|
`OutputCodec` int(10) unsigned NOT NULL default 0,
|
||||||
|
@ -480,6 +485,7 @@ CREATE TABLE `Monitors` (
|
||||||
`StreamReplayBuffer` int(10) unsigned NOT NULL default '1000',
|
`StreamReplayBuffer` int(10) unsigned NOT NULL default '1000',
|
||||||
`AlarmFrameCount` smallint(5) unsigned NOT NULL default '1',
|
`AlarmFrameCount` smallint(5) unsigned NOT NULL default '1',
|
||||||
`SectionLength` int(10) unsigned NOT NULL default '600',
|
`SectionLength` int(10) unsigned NOT NULL default '600',
|
||||||
|
`MinSectionLength` int(10) unsigned NOT NULL default '10',
|
||||||
`FrameSkip` smallint(5) unsigned NOT NULL default '0',
|
`FrameSkip` smallint(5) unsigned NOT NULL default '0',
|
||||||
`MotionFrameSkip` smallint(5) unsigned NOT NULL default '0',
|
`MotionFrameSkip` smallint(5) unsigned NOT NULL default '0',
|
||||||
`AnalysisFPSLimit` decimal(5,2) default NULL,
|
`AnalysisFPSLimit` decimal(5,2) default NULL,
|
||||||
|
@ -587,6 +593,7 @@ CREATE INDEX `Servers_Name_idx` ON `Servers` (`Name`);
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `Stats`;
|
DROP TABLE IF EXISTS `Stats`;
|
||||||
CREATE TABLE `Stats` (
|
CREATE TABLE `Stats` (
|
||||||
|
`Id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`MonitorId` int(10) unsigned NOT NULL default '0',
|
`MonitorId` int(10) unsigned NOT NULL default '0',
|
||||||
`ZoneId` int(10) unsigned NOT NULL default '0',
|
`ZoneId` int(10) unsigned NOT NULL default '0',
|
||||||
`EventId` BIGINT UNSIGNED NOT NULL,
|
`EventId` BIGINT UNSIGNED NOT NULL,
|
||||||
|
@ -603,6 +610,7 @@ CREATE TABLE `Stats` (
|
||||||
`MinY` smallint(5) unsigned NOT NULL default '0',
|
`MinY` smallint(5) unsigned NOT NULL default '0',
|
||||||
`MaxY` smallint(5) unsigned NOT NULL default '0',
|
`MaxY` smallint(5) unsigned NOT NULL default '0',
|
||||||
`Score` smallint(5) unsigned NOT NULL default '0',
|
`Score` smallint(5) unsigned NOT NULL default '0',
|
||||||
|
PRIMARY KEY (`Id`),
|
||||||
KEY `EventId` (`EventId`),
|
KEY `EventId` (`EventId`),
|
||||||
KEY `MonitorId` (`MonitorId`),
|
KEY `MonitorId` (`MonitorId`),
|
||||||
KEY `ZoneId` (`ZoneId`)
|
KEY `ZoneId` (`ZoneId`)
|
||||||
|
@ -641,6 +649,8 @@ CREATE TABLE `Users` (
|
||||||
`System` enum('None','View','Edit') NOT NULL default 'None',
|
`System` enum('None','View','Edit') NOT NULL default 'None',
|
||||||
`MaxBandwidth` varchar(16),
|
`MaxBandwidth` varchar(16),
|
||||||
`MonitorIds` text,
|
`MonitorIds` text,
|
||||||
|
`TokenMinExpiry` BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
`APIEnabled` tinyint(3) UNSIGNED NOT NULL default 1,
|
||||||
PRIMARY KEY (`Id`),
|
PRIMARY KEY (`Id`),
|
||||||
UNIQUE KEY `UC_Username` (`Username`)
|
UNIQUE KEY `UC_Username` (`Username`)
|
||||||
) ENGINE=@ZM_MYSQL_ENGINE@;
|
) ENGINE=@ZM_MYSQL_ENGINE@;
|
||||||
|
@ -741,7 +751,7 @@ insert into Storage VALUES (NULL, '@ZM_DIR_EVENTS@', 'Default', 'local', NULL, N
|
||||||
--
|
--
|
||||||
-- Create a default admin user.
|
-- Create a default admin user.
|
||||||
--
|
--
|
||||||
insert into Users VALUES (NULL,'admin',password('admin'),'',1,'View','Edit','Edit','Edit','Edit','Edit','Edit','','');
|
insert into Users VALUES (NULL,'admin','$2b$12$NHZsm6AM2f2LQVROriz79ul3D6DnmFiZC.ZK5eqbF.ZWfwH9bqUJ6','',1,'View','Edit','Edit','Edit','Edit','Edit','Edit','','',0,1);
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Add a sample filter to purge the oldest 100 events when the disk is 95% full
|
-- Add a sample filter to purge the oldest 100 events when the disk is 95% full
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
SET @s = (SELECT IF(
|
||||||
|
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'Monitors'
|
||||||
|
AND column_name = 'MinSectionLength'
|
||||||
|
) > 0,
|
||||||
|
"SELECT 'Column MinSectionLength already exists in Monitors'",
|
||||||
|
"ALTER TABLE Monitors ADD `MinSectionLength` int(10) unsigned NOT NULL default '10' AFTER SectionLength"
|
||||||
|
));
|
||||||
|
|
||||||
|
PREPARE stmt FROM @s;
|
||||||
|
EXECUTE stmt;
|
|
@ -0,0 +1,24 @@
|
||||||
|
|
||||||
|
SET @s = (SELECT IF(
|
||||||
|
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'Monitors'
|
||||||
|
AND column_name = 'DecoderHWAccelName'
|
||||||
|
) > 0,
|
||||||
|
"SELECT 'Column DecoderHWAccelName already exists in Monitors'",
|
||||||
|
"ALTER TABLE Monitors ADD `DecoderHWAccelName` varchar(64) AFTER `Deinterlacing`"
|
||||||
|
));
|
||||||
|
|
||||||
|
PREPARE stmt FROM @s;
|
||||||
|
EXECUTE stmt;
|
||||||
|
|
||||||
|
SET @s = (SELECT IF(
|
||||||
|
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'Monitors'
|
||||||
|
AND column_name = 'DecoderHWAccelDevice'
|
||||||
|
) > 0,
|
||||||
|
"SELECT 'Column DecoderHWAccelDevice already exists in Monitors'",
|
||||||
|
"ALTER TABLE Monitors ADD `DecoderHWAccelDevice` varchar(255) AFTER `DecoderHWAccelName`"
|
||||||
|
));
|
||||||
|
|
||||||
|
PREPARE stmt FROM @s;
|
||||||
|
EXECUTE stmt;
|
|
@ -0,0 +1,27 @@
|
||||||
|
--
|
||||||
|
-- Add primary keys for Logs and Stats tables
|
||||||
|
--
|
||||||
|
|
||||||
|
SET @s = (SELECT IF(
|
||||||
|
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'Logs'
|
||||||
|
AND column_name = 'Id'
|
||||||
|
) > 0,
|
||||||
|
"SELECT 'Column Id already exists in Logs'",
|
||||||
|
"ALTER TABLE `Logs` ADD COLUMN `Id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT FIRST, ADD PRIMARY KEY (`Id`)"
|
||||||
|
));
|
||||||
|
|
||||||
|
PREPARE stmt FROM @s;
|
||||||
|
EXECUTE stmt;
|
||||||
|
|
||||||
|
SET @s = (SELECT IF(
|
||||||
|
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'Stats'
|
||||||
|
AND column_name = 'Id'
|
||||||
|
) > 0,
|
||||||
|
"SELECT 'Column Id already exists in Stats'",
|
||||||
|
"ALTER TABLE `Stats` ADD COLUMN `Id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT FIRST, ADD PRIMARY KEY (`Id`)"
|
||||||
|
));
|
||||||
|
|
||||||
|
PREPARE stmt FROM @s;
|
||||||
|
EXECUTE stmt;
|
|
@ -0,0 +1,6 @@
|
||||||
|
--
|
||||||
|
-- Add primary keys for Logs and Stats tables
|
||||||
|
--
|
||||||
|
|
||||||
|
SELECT "Modifying Monitors MaxFPS to DECIMAL(5,3)";
|
||||||
|
ALTER TABLE `Monitors` MODIFY `MaxFPS` decimal(5,3) default NULL;
|
|
@ -0,0 +1,51 @@
|
||||||
|
--
|
||||||
|
-- Add CopyTo action to Filters
|
||||||
|
--
|
||||||
|
|
||||||
|
SET @s = (SELECT IF(
|
||||||
|
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'Filters'
|
||||||
|
AND column_name = 'AutoCopy'
|
||||||
|
) > 0,
|
||||||
|
"SELECT 'Column AutoCopy already exists in Filters'",
|
||||||
|
"ALTER TABLE Filters ADD `AutoCopy` tinyint(3) unsigned NOT NULL default '0' AFTER `AutoMove`"
|
||||||
|
));
|
||||||
|
|
||||||
|
PREPARE stmt FROM @s;
|
||||||
|
EXECUTE stmt;
|
||||||
|
|
||||||
|
SET @s = (SELECT IF(
|
||||||
|
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'Filters'
|
||||||
|
AND column_name = 'AutoCopyTo'
|
||||||
|
) > 0,
|
||||||
|
"SELECT 'Column AutoCopyTo already exists in Filters'",
|
||||||
|
"ALTER TABLE Filters ADD `AutoCopyTo` smallint(5) unsigned NOT NULL default '0' AFTER `AutoCopy`"
|
||||||
|
));
|
||||||
|
|
||||||
|
PREPARE stmt FROM @s;
|
||||||
|
EXECUTE stmt;
|
||||||
|
|
||||||
|
SET @s = (SELECT IF(
|
||||||
|
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'Filters'
|
||||||
|
AND column_name = 'Query_json'
|
||||||
|
) > 0,
|
||||||
|
"SELECT 'Column Query_json already exists in Filters'",
|
||||||
|
"ALTER TABLE `Filters` Change `Query` `Query_json` text NOT NULL"
|
||||||
|
));
|
||||||
|
|
||||||
|
PREPARE stmt FROM @s;
|
||||||
|
EXECUTE stmt;
|
||||||
|
|
||||||
|
SET @s = (SELECT IF(
|
||||||
|
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'Events'
|
||||||
|
AND column_name = 'SecondaryStorageId'
|
||||||
|
) > 0,
|
||||||
|
"SELECT 'Column SecondaryStorageId already exists in Events'",
|
||||||
|
"ALTER TABLE `Events` ADD `SecondaryStorageId` smallint(5) unsigned default 0 AFTER `StorageId`"
|
||||||
|
));
|
||||||
|
|
||||||
|
PREPARE stmt FROM @s;
|
||||||
|
EXECUTE stmt;
|
|
@ -0,0 +1,27 @@
|
||||||
|
--
|
||||||
|
-- Add per user API enable/disable and ability to set a minimum issued time for tokens
|
||||||
|
--
|
||||||
|
|
||||||
|
SET @s = (SELECT IF(
|
||||||
|
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'Users'
|
||||||
|
AND column_name = 'TokenMinExpiry'
|
||||||
|
) > 0,
|
||||||
|
"SELECT 'Column TokenMinExpiry already exists in Users'",
|
||||||
|
"ALTER TABLE Users ADD `TokenMinExpiry` BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER `MonitorIds`"
|
||||||
|
));
|
||||||
|
|
||||||
|
PREPARE stmt FROM @s;
|
||||||
|
EXECUTE stmt;
|
||||||
|
|
||||||
|
SET @s = (SELECT IF(
|
||||||
|
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'Users'
|
||||||
|
AND column_name = 'APIEnabled'
|
||||||
|
) > 0,
|
||||||
|
"SELECT 'Column APIEnabled already exists in Users'",
|
||||||
|
"ALTER TABLE Users ADD `APIEnabled` tinyint(3) UNSIGNED NOT NULL default 1 AFTER `TokenMinExpiry`"
|
||||||
|
));
|
||||||
|
|
||||||
|
PREPARE stmt FROM @s;
|
||||||
|
EXECUTE stmt;
|
|
@ -21,11 +21,13 @@ Build-Depends: debhelper (>= 9), cmake
|
||||||
, libphp-serialization-perl
|
, libphp-serialization-perl
|
||||||
, libdate-manip-perl, libmime-lite-perl, libmime-tools-perl, libdbd-mysql-perl
|
, libdate-manip-perl, libmime-lite-perl, libmime-tools-perl, libdbd-mysql-perl
|
||||||
, libwww-perl, libarchive-tar-perl, libarchive-zip-perl, libdevice-serialport-perl
|
, libwww-perl, libarchive-tar-perl, libarchive-zip-perl, libdevice-serialport-perl
|
||||||
, libmodule-load-perl, libsys-mmap-perl, libjson-any-perl
|
, libmodule-load-perl, libsys-mmap-perl, libjson-maybexs-perl
|
||||||
, libnet-sftp-foreign-perl, libio-pty-perl, libexpect-perl
|
, libnet-sftp-foreign-perl, libio-pty-perl, libexpect-perl
|
||||||
, libdata-dump-perl, libclass-std-fast-perl, libsoap-wsdl-perl, libio-socket-multicast-perl, libdigest-sha-perl
|
, libdata-dump-perl, libclass-std-fast-perl, libsoap-wsdl-perl, libio-socket-multicast-perl, libdigest-sha-perl
|
||||||
, libsys-cpu-perl, libsys-meminfo-perl
|
, libsys-cpu-perl, libsys-meminfo-perl
|
||||||
, libdata-uuid-perl
|
, libdata-uuid-perl
|
||||||
|
, libssl-dev
|
||||||
|
, libcrypt-eksblowfish-perl, libdata-entropy-perl
|
||||||
Standards-Version: 3.9.4
|
Standards-Version: 3.9.4
|
||||||
|
|
||||||
Package: zoneminder
|
Package: zoneminder
|
||||||
|
@ -37,7 +39,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
|
||||||
, libphp-serialization-perl
|
, libphp-serialization-perl
|
||||||
, libdate-manip-perl, libmime-lite-perl, libmime-tools-perl, libdbd-mysql-perl
|
, libdate-manip-perl, libmime-lite-perl, libmime-tools-perl, libdbd-mysql-perl
|
||||||
, libwww-perl, libarchive-tar-perl, libarchive-zip-perl, libdevice-serialport-perl
|
, libwww-perl, libarchive-tar-perl, libarchive-zip-perl, libdevice-serialport-perl
|
||||||
, libmodule-load-perl, libsys-mmap-perl, libjson-any-perl, libjson-maybexs-perl
|
, libmodule-load-perl, libsys-mmap-perl, libjson-maybexs-perl
|
||||||
, libnet-sftp-foreign-perl, libio-pty-perl, libexpect-perl
|
, libnet-sftp-foreign-perl, libio-pty-perl, libexpect-perl
|
||||||
, libdata-dump-perl, libclass-std-fast-perl, libsoap-wsdl-perl, libio-socket-multicast-perl, libdigest-sha-perl
|
, libdata-dump-perl, libclass-std-fast-perl, libsoap-wsdl-perl, libio-socket-multicast-perl, libdigest-sha-perl
|
||||||
, libsys-cpu-perl, libsys-meminfo-perl
|
, libsys-cpu-perl, libsys-meminfo-perl
|
||||||
|
@ -51,6 +53,9 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
|
||||||
, zip
|
, zip
|
||||||
, libvlccore5 | libvlccore7 | libvlccore8, libvlc5
|
, libvlccore5 | libvlccore7 | libvlccore8, libvlc5
|
||||||
, libpolkit-gobject-1-0, php5-gd
|
, libpolkit-gobject-1-0, php5-gd
|
||||||
|
, libssl
|
||||||
|
,libcrypt-eksblowfish-perl, libdata-entropy-perl
|
||||||
|
|
||||||
Recommends: mysql-server | mariadb-server
|
Recommends: mysql-server | mariadb-server
|
||||||
Description: Video camera security and surveillance solution
|
Description: Video camera security and surveillance solution
|
||||||
ZoneMinder is intended for use in single or multi-camera video security
|
ZoneMinder is intended for use in single or multi-camera video security
|
||||||
|
|
|
@ -31,6 +31,10 @@ if [ "$1" = "configure" ]; then
|
||||||
# test if database if already present...
|
# test if database if already present...
|
||||||
if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then
|
if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then
|
||||||
cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf
|
cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Error creating db."
|
||||||
|
exit 1;
|
||||||
|
fi
|
||||||
# This creates the user.
|
# This creates the user.
|
||||||
echo "grant lock tables, alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql
|
echo "grant lock tables, alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql
|
||||||
else
|
else
|
||||||
|
|
|
@ -21,10 +21,11 @@ override_dh_auto_configure:
|
||||||
-DZM_CGIDIR=/usr/lib/zoneminder/cgi-bin \
|
-DZM_CGIDIR=/usr/lib/zoneminder/cgi-bin \
|
||||||
-DZM_WEB_USER=www-data \
|
-DZM_WEB_USER=www-data \
|
||||||
-DZM_WEB_GROUP=www-data \
|
-DZM_WEB_GROUP=www-data \
|
||||||
-DZM_CONFIG_SUBDIR="/etc/zm/conf.d" \
|
-DZM_CONFIG_SUBDIR="/etc/zm/conf.d" \
|
||||||
-DZM_CONFIG_DIR="/etc/zm" \
|
-DZM_CONFIG_DIR="/etc/zm" \
|
||||||
-DZM_DIR_EVENTS="/var/cache/zoneminder/events" \
|
-DZM_DIR_EVENTS="/var/cache/zoneminder/events" \
|
||||||
-DZM_PATH_ZMS="/zm/cgi-bin/nph-zms"
|
-DZM_PATH_SHUTDOWN="/sbin/shutdown" \
|
||||||
|
-DZM_PATH_ZMS="/zm/cgi-bin/nph-zms"
|
||||||
|
|
||||||
override_dh_auto_install:
|
override_dh_auto_install:
|
||||||
dh_auto_install --buildsystem=cmake
|
dh_auto_install --buildsystem=cmake
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
%global _hardened_build 1
|
%global _hardened_build 1
|
||||||
|
|
||||||
Name: zoneminder
|
Name: zoneminder
|
||||||
Version: 1.33.8
|
Version: 1.33.14
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: A camera monitoring and analysis tool
|
Summary: A camera monitoring and analysis tool
|
||||||
Group: System Environment/Daemons
|
Group: System Environment/Daemons
|
||||||
|
@ -411,6 +411,15 @@ EOF
|
||||||
%dir %attr(755,nginx,nginx) %{_localstatedir}/spool/zoneminder-upload
|
%dir %attr(755,nginx,nginx) %{_localstatedir}/spool/zoneminder-upload
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Sun Aug 11 2019 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.14-1
|
||||||
|
- Bump to 1.33.13 Development
|
||||||
|
|
||||||
|
* Sun Jul 07 2019 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.12-1
|
||||||
|
- Bump to 1.33.12 Development
|
||||||
|
|
||||||
|
* Sun Jun 23 2019 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.9-1
|
||||||
|
- Bump to 1.33.9 Development
|
||||||
|
|
||||||
* Tue Apr 30 2019 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.8-1
|
* Tue Apr 30 2019 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.8-1
|
||||||
- Bump to 1.33.8 Development
|
- Bump to 1.33.8 Development
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,9 @@ Build-Depends: debhelper (>= 9), python-sphinx | python3-sphinx, apache2-dev, dh
|
||||||
,libsys-mmap-perl [!hurd-any]
|
,libsys-mmap-perl [!hurd-any]
|
||||||
,libwww-perl
|
,libwww-perl
|
||||||
,libdata-uuid-perl
|
,libdata-uuid-perl
|
||||||
|
,libssl-dev
|
||||||
|
,libcrypt-eksblowfish-perl
|
||||||
|
,libdata-entropy-perl
|
||||||
# Unbundled (dh_linktree):
|
# Unbundled (dh_linktree):
|
||||||
,libjs-jquery
|
,libjs-jquery
|
||||||
,libjs-mootools
|
,libjs-mootools
|
||||||
|
@ -49,7 +52,6 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
|
||||||
,libdbd-mysql-perl
|
,libdbd-mysql-perl
|
||||||
,libdevice-serialport-perl
|
,libdevice-serialport-perl
|
||||||
,libimage-info-perl
|
,libimage-info-perl
|
||||||
,libjson-any-perl
|
|
||||||
,libjson-maybexs-perl
|
,libjson-maybexs-perl
|
||||||
,libsys-mmap-perl [!hurd-any]
|
,libsys-mmap-perl [!hurd-any]
|
||||||
,liburi-encode-perl
|
,liburi-encode-perl
|
||||||
|
@ -63,8 +65,12 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
|
||||||
,policykit-1
|
,policykit-1
|
||||||
,rsyslog | system-log-daemon
|
,rsyslog | system-log-daemon
|
||||||
,zip
|
,zip
|
||||||
,libdata-dump-perl, libclass-std-fast-perl, libsoap-wsdl-perl, libio-socket-multicast-perl, libdigest-sha-perl
|
,libdata-dump-perl, libclass-std-fast-perl, libsoap-wsdl-perl
|
||||||
, libsys-cpu-perl, libsys-meminfo-perl
|
,libio-socket-multicast-perl, libdigest-sha-perl
|
||||||
|
,libsys-cpu-perl, libsys-meminfo-perl
|
||||||
|
,libssl | libssl1.0.0
|
||||||
|
,libcrypt-eksblowfish-perl
|
||||||
|
,libdata-entropy-perl
|
||||||
Recommends: ${misc:Recommends}
|
Recommends: ${misc:Recommends}
|
||||||
,libapache2-mod-php5 | php5-fpm
|
,libapache2-mod-php5 | php5-fpm
|
||||||
,mysql-server | virtual-mysql-server
|
,mysql-server | virtual-mysql-server
|
||||||
|
@ -91,7 +97,7 @@ Description: video camera security and surveillance solution
|
||||||
# ,libdbd-mysql-perl
|
# ,libdbd-mysql-perl
|
||||||
# ,libdevice-serialport-perl
|
# ,libdevice-serialport-perl
|
||||||
# ,libimage-info-perl
|
# ,libimage-info-perl
|
||||||
# ,libjson-any-perl
|
# ,libjson-maybexs-perl
|
||||||
# ,libsys-mmap-perl [!hurd-any]
|
# ,libsys-mmap-perl [!hurd-any]
|
||||||
# ,liburi-encode-perl
|
# ,liburi-encode-perl
|
||||||
# ,libwww-perl
|
# ,libwww-perl
|
||||||
|
|
|
@ -27,6 +27,7 @@ override_dh_auto_configure:
|
||||||
-DZM_CGIDIR="/usr/lib/zoneminder/cgi-bin" \
|
-DZM_CGIDIR="/usr/lib/zoneminder/cgi-bin" \
|
||||||
-DZM_CACHEDIR="/var/cache/zoneminder/cache" \
|
-DZM_CACHEDIR="/var/cache/zoneminder/cache" \
|
||||||
-DZM_DIR_EVENTS="/var/cache/zoneminder/events" \
|
-DZM_DIR_EVENTS="/var/cache/zoneminder/events" \
|
||||||
|
-DZM_PATH_SHUTDOWN="/sbin/shutdown" \
|
||||||
-DZM_PATH_ZMS="/zm/cgi-bin/nph-zms"
|
-DZM_PATH_ZMS="/zm/cgi-bin/nph-zms"
|
||||||
|
|
||||||
override_dh_clean:
|
override_dh_clean:
|
||||||
|
|
|
@ -34,6 +34,10 @@ if [ "$1" = "configure" ]; then
|
||||||
# test if database if already present...
|
# test if database if already present...
|
||||||
if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then
|
if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then
|
||||||
cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf
|
cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Error creating db."
|
||||||
|
exit 1;
|
||||||
|
fi
|
||||||
# This creates the user.
|
# This creates the user.
|
||||||
echo "grant lock tables, alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql
|
echo "grant lock tables, alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql
|
||||||
else
|
else
|
||||||
|
|
|
@ -30,6 +30,9 @@ Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apa
|
||||||
,libsys-mmap-perl [!hurd-any]
|
,libsys-mmap-perl [!hurd-any]
|
||||||
,libwww-perl
|
,libwww-perl
|
||||||
,libdata-uuid-perl
|
,libdata-uuid-perl
|
||||||
|
,libssl-dev
|
||||||
|
,libcrypt-eksblowfish-perl
|
||||||
|
,libdata-entropy-perl
|
||||||
# Unbundled (dh_linktree):
|
# Unbundled (dh_linktree):
|
||||||
,libjs-jquery
|
,libjs-jquery
|
||||||
,libjs-mootools
|
,libjs-mootools
|
||||||
|
@ -42,7 +45,7 @@ Package: zoneminder
|
||||||
Architecture: any
|
Architecture: any
|
||||||
Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
|
Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
|
||||||
,javascript-common
|
,javascript-common
|
||||||
,libmp4v2-2, libx264-142|libx264-148|libx264-152
|
,libmp4v2-2, libx264-142|libx264-148|libx264-152|libx264-155
|
||||||
,libswscale-ffmpeg3|libswscale4|libswscale3|libswscale5
|
,libswscale-ffmpeg3|libswscale4|libswscale3|libswscale5
|
||||||
,libswresample2|libswresample3|libswresample24|libswresample-ffmpeg1
|
,libswresample2|libswresample3|libswresample24|libswresample-ffmpeg1
|
||||||
,ffmpeg | libav-tools
|
,ffmpeg | libav-tools
|
||||||
|
@ -55,7 +58,6 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
|
||||||
,libdbd-mysql-perl
|
,libdbd-mysql-perl
|
||||||
,libdevice-serialport-perl
|
,libdevice-serialport-perl
|
||||||
,libimage-info-perl
|
,libimage-info-perl
|
||||||
,libjson-any-perl
|
|
||||||
,libjson-maybexs-perl
|
,libjson-maybexs-perl
|
||||||
,libsys-mmap-perl [!hurd-any]
|
,libsys-mmap-perl [!hurd-any]
|
||||||
,liburi-encode-perl
|
,liburi-encode-perl
|
||||||
|
@ -76,6 +78,9 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
|
||||||
,rsyslog | system-log-daemon
|
,rsyslog | system-log-daemon
|
||||||
,zip
|
,zip
|
||||||
,libpcre3
|
,libpcre3
|
||||||
|
,libssl | libssl1.0.0 | libssl1.1
|
||||||
|
,libcrypt-eksblowfish-perl
|
||||||
|
,libdata-entropy-perl
|
||||||
Recommends: ${misc:Recommends}
|
Recommends: ${misc:Recommends}
|
||||||
,libapache2-mod-php5 | libapache2-mod-php | php5-fpm | php-fpm
|
,libapache2-mod-php5 | libapache2-mod-php | php5-fpm | php-fpm
|
||||||
,mysql-server | mariadb-server | virtual-mysql-server
|
,mysql-server | mariadb-server | virtual-mysql-server
|
||||||
|
@ -103,7 +108,7 @@ Description: video camera security and surveillance solution
|
||||||
# ,libdbd-mysql-perl
|
# ,libdbd-mysql-perl
|
||||||
# ,libdevice-serialport-perl
|
# ,libdevice-serialport-perl
|
||||||
# ,libimage-info-perl
|
# ,libimage-info-perl
|
||||||
# ,libjson-any-perl
|
# ,libjson-maybexs-perl
|
||||||
# ,libsys-mmap-perl [!hurd-any]
|
# ,libsys-mmap-perl [!hurd-any]
|
||||||
# ,liburi-encode-perl
|
# ,liburi-encode-perl
|
||||||
# ,libwww-perl
|
# ,libwww-perl
|
||||||
|
|
|
@ -27,6 +27,7 @@ override_dh_auto_configure:
|
||||||
-DZM_CGIDIR="/usr/lib/zoneminder/cgi-bin" \
|
-DZM_CGIDIR="/usr/lib/zoneminder/cgi-bin" \
|
||||||
-DZM_CACHEDIR="/var/cache/zoneminder/cache" \
|
-DZM_CACHEDIR="/var/cache/zoneminder/cache" \
|
||||||
-DZM_DIR_EVENTS="/var/cache/zoneminder/events" \
|
-DZM_DIR_EVENTS="/var/cache/zoneminder/events" \
|
||||||
|
-DZM_PATH_SHUTDOWN="/sbin/shutdown" \
|
||||||
-DZM_PATH_ZMS="/zm/cgi-bin/nph-zms"
|
-DZM_PATH_ZMS="/zm/cgi-bin/nph-zms"
|
||||||
|
|
||||||
override_dh_clean:
|
override_dh_clean:
|
||||||
|
|
|
@ -56,6 +56,10 @@ if [ "$1" = "configure" ]; then
|
||||||
if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then
|
if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then
|
||||||
echo "Creating zm db"
|
echo "Creating zm db"
|
||||||
cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf
|
cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Error creating db."
|
||||||
|
exit 1;
|
||||||
|
fi
|
||||||
# This creates the user.
|
# This creates the user.
|
||||||
echo "grant lock tables,alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql
|
echo "grant lock tables,alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
d /var/run/zm 0755 www-data www-data
|
d /run/zm 0755 www-data www-data
|
||||||
d /tmp/zm 0755 www-data www-data
|
d /tmp/zm 0755 www-data www-data
|
||||||
d /var/tmp/zm 0755 www-data www-data
|
d /var/tmp/zm 0755 www-data www-data
|
||||||
d /var/cache/zoneminder/cache 0755 www-data www-data
|
d /var/cache/zoneminder/cache 0755 www-data www-data
|
||||||
|
|
366
docs/api.rst
366
docs/api.rst
|
@ -1,10 +1,12 @@
|
||||||
|
|
||||||
API
|
API
|
||||||
====
|
====
|
||||||
|
|
||||||
This document will provide an overview of ZoneMinder's API. This is work in progress.
|
This document will provide an overview of ZoneMinder's API.
|
||||||
|
|
||||||
Overview
|
Overview
|
||||||
^^^^^^^^
|
^^^^^^^^
|
||||||
|
|
||||||
In an effort to further 'open up' ZoneMinder, an API was needed. This will
|
In an effort to further 'open up' ZoneMinder, an API was needed. This will
|
||||||
allow quick integration with and development of ZoneMinder.
|
allow quick integration with and development of ZoneMinder.
|
||||||
|
|
||||||
|
@ -12,178 +14,178 @@ The API is built in CakePHP and lives under the ``/api`` directory. It
|
||||||
provides a RESTful service and supports CRUD (create, retrieve, update, delete)
|
provides a RESTful service and supports CRUD (create, retrieve, update, delete)
|
||||||
functions for Monitors, Events, Frames, Zones and Config.
|
functions for Monitors, Events, Frames, Zones and Config.
|
||||||
|
|
||||||
Streaming Interface
|
API evolution
|
||||||
^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
Developers working on their application often ask if there is an "API" to receive live streams, or recorded event streams.
|
|
||||||
It is possible to stream both live and recorded streams. This isn't strictly an "API" per-se (that is, it is not integrated
|
|
||||||
into the Cake PHP based API layer discussed here) and also why we've used the term "Interface" instead of an "API".
|
|
||||||
|
|
||||||
Live Streams
|
The ZoneMinder API has evolved over time. Broadly speaking the iterations were as follows:
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
What you need to know is that if you want to display "live streams", ZoneMinder sends you streaming JPEG images (MJPEG)
|
|
||||||
which can easily be rendered in a browser using an ``img src`` tag.
|
|
||||||
|
|
||||||
For example:
|
* Prior to version 1.29, there really was no API layer. Users had to use the same URLs that the web console used to 'mimic' operations, or use an XML skin
|
||||||
|
* Starting version 1.29, a v1.0 CakePHP based API was released which continues to evolve over time. From a security perspective, it still tied into ZM auth and required client cookies for many operations. Primarily, two authentication modes were offered:
|
||||||
|
* You use cookies to maintain session state (`ZM_SESS_ID`)
|
||||||
|
* You use an authentication hash to validate yourself, which included encoding personal information and time stamps which at times caused timing validation issues, especially for mobile consumers
|
||||||
|
* Starting version 1.34, ZoneMinder has introduced a new "token" based system which is based JWT. We have given it a '2.0' version ID. These tokens don't encode any personal data and can be statelessly passed around per request. It introduces concepts like access tokens, refresh tokens and per user level API revocation to manage security better. The internal components of ZoneMinder all support this new scheme now and if you are using the APIs we strongly recommend you migrate to 1.34 and use this new token system (as a side note, 1.34 also moves from MYSQL PASSWORD to Bcrypt for passwords, which is also a good reason why you should migate).
|
||||||
|
* Note that as of 1.34, both versions of API access will work (tokens and the older auth hash mechanism).
|
||||||
|
|
||||||
::
|
.. NOTE::
|
||||||
|
For the rest of the document, we will specifically highlight v2.0 only features. If you don't see a special mention, assume it applies for both API versions.
|
||||||
|
|
||||||
<img src="https://yourserver/zm/cgi-bin/nph-zms?scale=50&width=640p&height=480px&mode=jpeg&maxfps=5&buffer=1000&&monitor=1&auth=b54a589e09f330498f4ae2203&connkey=36139" />
|
|
||||||
|
|
||||||
will display a live feed from monitor id 1, scaled down by 50% in quality and resized to 640x480px.
|
|
||||||
|
|
||||||
* This assumes ``/zm/cgi-bin`` is your CGI_BIN path. Change it to what is correct in your system
|
|
||||||
* The "auth" token you see above is required if you use ZoneMinder authentication. To understand how to get the auth token, please read the "Login, Logout & API security" section below.
|
|
||||||
* The "connkey" parameter is essentially a random number which uniquely identifies a stream. If you don't specify a connkey, ZM will generate its own. It is recommended to generate a connkey because you can then use it to "control" the stream (pause/resume etc.)
|
|
||||||
* Instead of dealing with the "auth" token, you can also use ``&user=username&pass=password`` where "username" and "password" are your ZoneMinder username and password respectively. Note that this is not recommended because you are transmitting them in a URL and even if you use HTTPS, they may show up in web server logs.
|
|
||||||
|
|
||||||
|
|
||||||
PTZ on live streams
|
|
||||||
-------------------
|
|
||||||
PTZ commands are pretty cryptic in ZoneMinder. This is not meant to be an exhaustive guide, but just something to whet your appetite:
|
|
||||||
|
|
||||||
|
|
||||||
Lets assume you have a monitor, with ID=6. Let's further assume you want to pan it left.
|
|
||||||
|
|
||||||
You'd need to send a:
|
|
||||||
``POST`` command to ``https://yourserver/zm/index.php`` with the following data payload in the command (NOT in the URL)
|
|
||||||
|
|
||||||
``view=request&request=control&id=6&control=moveConLeft&xge=30&yge=30``
|
|
||||||
|
|
||||||
Obviously, if you are using authentication, you need to be logged in for this to work.
|
|
||||||
|
|
||||||
Like I said, at this stage, this is only meant to get you started. Explore the ZoneMinder code and use "Inspect source" as you use PTZ commands in the ZoneMinder source code.
|
|
||||||
`control_functions.php <https://github.com/ZoneMinder/zoneminder/blob/10531df54312f52f0f32adec3d4720c063897b62/web/skins/classic/includes/control_functions.php>`__ is a great place to start.
|
|
||||||
|
|
||||||
|
|
||||||
Pre-recorded (past event) streams
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Similar to live playback, if you have chosen to store events in JPEG mode, you can play it back using:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
<img src="https://yourserver/zm/cgi-bin/nph-zms?mode=jpeg&frame=1&replay=none&source=event&event=293820&connkey=77493&auth=b54a58f5f4ae2203" />
|
|
||||||
|
|
||||||
|
|
||||||
* This assumes ``/zm/cgi-bin`` is your CGI_BIN path. Change it to what is correct in your system
|
|
||||||
* This will playback event 293820, starting from frame 1 as an MJPEG stream
|
|
||||||
* Like before, you can add more parameters like ``scale`` etc.
|
|
||||||
* auth and connkey have the same meaning as before, and yes, you can replace auth by ``&user=usename&pass=password`` as before and the same security concerns cited above apply.
|
|
||||||
|
|
||||||
If instead, you have chosen to use the MP4 (Video) storage mode for events, you can directly play back the saved video file:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
<video src="https://yourserver/zm/index.php?view=view_video&eid=294690&auth=33f3d558af84cf08" type="video/mp4"></video>
|
|
||||||
|
|
||||||
* This will play back the video recording for event 294690
|
|
||||||
|
|
||||||
What other parameters are supported?
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
The best way to answer this question is to play with ZoneMinder console. Open a browser, play back live or recorded feed, and do an "Inspect Source" to see what parameters
|
|
||||||
are generated. Change and observe.
|
|
||||||
|
|
||||||
|
|
||||||
Enabling API
|
Enabling API
|
||||||
^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
A default ZoneMinder installs with APIs enabled. You can explictly enable/disable the APIs
|
|
||||||
via the Options->System menu by enabling/disabling ``OPT_USE_API``. Note that if you intend
|
|
||||||
to use APIs with 3rd party apps, such as zmNinja or others that use APIs, you should also
|
|
||||||
enable ``AUTH_HASH_LOGINS``.
|
|
||||||
|
|
||||||
Login, Logout & API Security
|
ZoneMinder comes with APIs enabled. To check if APIs are enabled, visit ``Options->System``. If ``OPT_USE_API`` is enabled, your APIs are active.
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
For v2.0 APIs, you have an additional option right below it - ``OPT_USE_LEGACY_API_AUTH`` which is enabled by default. When enabled, the `login.json` API (discussed later) will return both the old style (``auth=``) and new style (``token=``) credentials. The reason this is enabled by default is because any existing apps that use the API would break if they were not updated to use v2.0. (Note that zmNinja 1.3.057 and beyond will support tokens)
|
||||||
The APIs tie into ZoneMinder's existing security model. This means if you have
|
|
||||||
OPT_AUTH enabled, you need to log into ZoneMinder using the same browser you plan to
|
|
||||||
use the APIs from. If you are developing an app that relies on the API, you need
|
|
||||||
to do a POST login from the app into ZoneMinder before you can access the API.
|
|
||||||
|
|
||||||
Then, you need to re-use the authentication information of the login (returned as cookie states)
|
Enabling secret key
|
||||||
with subsequent APIs for the authentication information to flow through to the APIs.
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This means if you plan to use cuRL to experiment with these APIs, you first need to login:
|
* It is **important** that you create a "Secret Key". This needs to be a set of hard to guess characters, that only you know. ZoneMinder does not create a key for you. It is your responsibility to create it. If you haven't created one already, please do so by going to ``Options->Systems`` and populating ``AUTH_HASH_SECRET``. Don't forget to save.
|
||||||
|
* If you plan on using V2.0 token based security, **it is mandatory to populate this secret key**, as it is used to sign the token. If you don't, token authentication will fail. V1.0 did not mandate this requirement.
|
||||||
|
|
||||||
**Login process for ZoneMinder v1.32.0 and above**
|
|
||||||
|
Getting an API key
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
To get an API key:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
curl -XPOST -d "user=XXXX&pass=YYYY" -c cookies.txt http://yourzmip/zm/api/host/login.json
|
curl -XPOST [-c cookies.txt] -d "user=yourusername&pass=yourpassword" https://yourserver/zm/api/host/login.json
|
||||||
|
|
||||||
Staring ZM 1.32.0, you also have a `logout` API that basically clears your session. It looks like this:
|
|
||||||
|
The ``[-c cookies.txt]`` is optional, and will be explained in the next section.
|
||||||
|
|
||||||
|
This returns a payload like this for API v1.0:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
curl -b cookies.txt http://yourzmip/zm/api/host/logout.json
|
{
|
||||||
|
"credentials": "auth=05f3a50e8f7<deleted>063",
|
||||||
|
"append_password": 0,
|
||||||
|
"version": "1.33.9",
|
||||||
|
"apiversion": "1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
Or for API 2.0:
|
||||||
**Login process for older versions of ZoneMinder**
|
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
curl -d "username=XXXX&password=YYYY&action=login&view=console" -c cookies.txt http://yourzmip/zm/index.php
|
{
|
||||||
|
"access_token": "eyJ0eXAiOiJK<deleted>HE",
|
||||||
|
"access_token_expires": 3600,
|
||||||
|
"refresh_token": "eyJ0eXAiOi<deleted>mPs",
|
||||||
|
"refresh_token_expires": 86400,
|
||||||
|
"credentials": "auth=05f3a50e8f7<deleted>063", # only if OPT_USE_LEGACY_API_AUTH is enabled
|
||||||
|
"append_password": 0, # only if OPT_USE_LEGACY_API_AUTH is enabled
|
||||||
|
"version": "1.33.9",
|
||||||
|
"apiversion": "2.0"
|
||||||
|
}
|
||||||
|
|
||||||
The equivalent logout process for older versions of ZoneMinder is:
|
Using these keys with subsequent requests
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Once you have the keys (a.k.a credentials (v1.0, v2.0) or token (v2.0)) you should now supply that key to subsequent API calls like this:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
curl -XPOST -d "username=XXXX&password=YYYY&action=logout&view=console" -b cookies.txt http://yourzmip/zm/index.php
|
# RECOMMENDED: v2.0 token based
|
||||||
|
curl -XPOST https://yourserver/zm/api/monitors.json&token=<access_token>
|
||||||
|
|
||||||
replacing *XXXX* and *YYYY* with your username and password, respectively.
|
# or
|
||||||
|
|
||||||
Please make sure you do this in a directory where you have write permissions, otherwise cookies.txt will not be created
|
# v1.0 or 2.0 based API access (will only work if AUTH_HASH_LOGINS is enabled)
|
||||||
and the command will silently fail.
|
curl -XPOST -d "auth=<hex digits from 'credentials'>" https://yourserver/zm/api/monitors.json
|
||||||
|
|
||||||
|
# or
|
||||||
|
|
||||||
|
curl -XGET https://yourserver/zm/api/monitors.json&auth=<hex digits from 'credentials'>
|
||||||
|
|
||||||
|
# or, if you specified -c cookies.txt in the original login request
|
||||||
|
|
||||||
|
curl -b cookies.txt -XGET https://yourserver/zm/api/monitors.json
|
||||||
|
|
||||||
|
|
||||||
What the "-c cookies.txt" does is store a cookie state reflecting that you have logged into ZM. You now need
|
.. NOTE::
|
||||||
to apply that cookie state to all subsequent APIs. You do that by using a '-b cookies.txt' to subsequent APIs if you are
|
ZoneMinder's API layer allows API keys to be encoded either as a query parameter or as a data payload. If you don't pass keys, you could use cookies (not recommended as a general approach)
|
||||||
using CuRL like so:
|
|
||||||
|
|
||||||
|
Key lifetime (v1.0)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
If you are using the old credentials mechanism present in v1.0, then the credentials will time out based on PHP session timeout (if you are using cookies), or the value of ``AUTH_HASH_TTL`` (if you are using ``auth=`` and have enabled ``AUTH_HASH_LOGINS``) which defaults to 2 hours. Note that there is no way to look at the hash and decipher how much time is remaining. So it is your responsibility to record the time you got the hash and assume it was generated at the time you got it and re-login before that time expires.
|
||||||
|
|
||||||
|
Key lifetime (v2.0)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
In version 2.0, it is easy to know when a key will expire before you use it. You can find that out from the ``access_token_expires`` and ``refresh_token_exipres`` values (in seconds) after you decode the JWT key (there are JWT decode libraries for every language you want). You should refresh the keys before the timeout occurs, or you will not be able to use the APIs.
|
||||||
|
|
||||||
|
Understanding access/refresh tokens (v2.0)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
If you are using V2.0, then you need to know how to use these tokens effectively:
|
||||||
|
|
||||||
|
* Access tokens are short lived. ZoneMinder issues access tokens that live for 3600 seconds (1 hour).
|
||||||
|
* Access tokens should be used for all subsequent API accesses.
|
||||||
|
* Refresh tokens should ONLY be used to generate new access tokens. For example, if an access token lives for 1 hour, before the hour completes, invoke the ``login.json`` API above with the refresh token to get a new access token. ZoneMinder issues refresh tokens that live for 24 hours.
|
||||||
|
* To generate a new refresh token before 24 hours are up, you will need to pass your user login and password to ``login.json``
|
||||||
|
|
||||||
|
**To Summarize:**
|
||||||
|
|
||||||
|
* Pass your ``username`` and ``password`` to ``login.json`` only once in 24 hours to renew your tokens
|
||||||
|
* Pass your "refresh token" to ``login.json`` once in two hours (or whatever you have set the value of ``AUTH_HASH_TTL`` to) to renew your ``access token``
|
||||||
|
* Use your ``access token`` for all API invocations.
|
||||||
|
|
||||||
|
In fact, V2.0 will reject your request (if it is not to ``login.json``) if it comes with a refresh token instead of an access token to discourage usage of this token when it should not be used.
|
||||||
|
|
||||||
|
This minimizes the amount of sensitive data that is sent over the wire and the lifetime durations are made so that if they get compromised, you can regenerate or invalidate them (more on this later)
|
||||||
|
|
||||||
|
Understanding key security
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
* Version 1.0 uses an MD5 hash to generate the credentials. The hash is computed over your secret key (if available), username, password and some time parameters (along with remote IP if enabled). This is not a secure/recommended hashing mechanism. If your auth hash is compromised, an attacker will be able to use your hash till it expires. To avoid this, you could disable the user in ZoneMinder. Furthermore, enabling remote IP (``AUTH_HASH_REMOTE_IP``) requires that you issue future requests from the same IP that generated the tokens. While this may be considered an additional layer for security, this can cause issues with mobile devices.
|
||||||
|
|
||||||
|
* Version 2.0 uses a different approach. The hash is a simple base64 encoded form of "claims", but signed with your secret key. Consider for example, the following access key:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
curl -b cookies.txt http://yourzmip/zm/api/monitors.json
|
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJab25lTWluZGVyIiwiaWF0IjoxNTU3OTQwNzUyLCJleHAiOjE1NTc5NDQzNTIsInVzZXIiOiJhZG1pbiIsInR5cGUiOiJhY2Nlc3MifQ.-5VOcpw3cFHiSTN5zfGDSrrPyVya1M8_2Anh5u6eNlI
|
||||||
|
|
||||||
This would return a list of monitors and pass on the authentication information to the ZM API layer.
|
If you were to use any `JWT token verifier <https://jwt.io>`__ it can easily decode that token and will show:
|
||||||
|
|
||||||
A deeper dive into the login process
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
As you might have seen above, there are two ways to login, one that uses the `login.json` API and the other that logs in using the ZM portal. If you are running ZoneMinder 1.32.0 and above, it is *strongly* recommended you use the `login.json` approach. The "old" approach will still work but is not as powerful as the API based login. Here are the reasons why:
|
|
||||||
|
|
||||||
* The "old" approach basically uses the same login webpage (`index.php`) that a user would log into when viewing the ZM console. This is not really using an API and more importantly, if you have additional components like reCAPTCHA enabled, this will not work. Using the API approach is much cleaner and will work irrespective of reCAPTCHA
|
|
||||||
|
|
||||||
* The new login API returns important information that you can use to stream videos as well, right after login. Consider for example, a typical response to the login API (`/login.json`):
|
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
{
|
{
|
||||||
"credentials": "auth=f5b9cf48693fe8552503c8ABCD5",
|
"iss": "ZoneMinder",
|
||||||
"append_password": 0,
|
"iat": 1557940752,
|
||||||
"version": "1.31.44",
|
"exp": 1557944352,
|
||||||
"apiversion": "1.0"
|
"user": "admin",
|
||||||
}
|
"type": "access"
|
||||||
|
}
|
||||||
In this example I have `OPT_AUTH` enabled in ZoneMinder and it returns my credential key. You can then use this key to stream images like so:
|
Invalid Signature
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
<img src="https://server/zm/cgi-bin/nph-zms?monitor=1&auth=<authval>" />
|
|
||||||
|
|
||||||
Where `authval` is the credentials returned to start streaming videos.
|
|
||||||
|
|
||||||
The `append_password` field will contain 1 when it is necessary for you to append your ZM password. This is the case when you set `AUTH_RELAY` in ZM options to "plain", for example. In that case, the `credentials` field may contain something like `&user=admin&pass=` and you have to add your password to that string.
|
|
||||||
|
|
||||||
|
|
||||||
.. NOTE:: It is recommended you invoke the `login` API once every 60 minutes to make sure the session stays alive. The same is true if you use the old login method too.
|
Don't be surprised. JWT tokens, by default, are `not meant to be encrypted <https://softwareengineering.stackexchange.com/questions/280257/json-web-token-why-is-the-payload-public>`__. It is just an assertion of a claim. It states that the issuer of this token was ZoneMinder,
|
||||||
|
It was issued at (iat) Wednesday, 2019-05-15 17:19:12 UTC and will expire on (exp) Wednesday, 2019-05-15 18:19:12 UTC. This token claims to be owned by an admin and is an access token. If your token were to be stolen, this information is available to the person who stole it. Note that there are no sensitive details like passwords in this claim.
|
||||||
|
|
||||||
|
However, that person will **not** have your secret key as part of this token and therefore, will NOT be able to create a new JWT token to get, say, a refresh token. They will however, be able to use your access token to access resources just like the auth hash above, till the access token expires (2 hrs). To revoke this token, you don't need to disable the user. Go to ``Options->API`` and tap on "Revoke All Access Tokens". This will invalidate the token immediately (this option will invalidate all tokens for all users, and new ones will need to be generated).
|
||||||
|
|
||||||
|
Over time, we will provide you with more fine grained access to these options.
|
||||||
|
|
||||||
|
**Summarizing good practices:**
|
||||||
|
|
||||||
|
* Use HTTPS, not HTTP
|
||||||
|
* If possible, use free services like `LetsEncrypt <https://letsencrypt.org>`__ instead of self-signed certificates (sometimes this is not possible)
|
||||||
|
* Keep your tokens as private as possible, and use them as recommended above
|
||||||
|
* If you believe your tokens are compromised, revoke them, but also check if your attacker has compromised more than you think (example, they may also have your username/password or access to your system via other exploits, in which case they can regenerate as many tokens/credentials as they want).
|
||||||
|
|
||||||
|
|
||||||
|
.. NOTE::
|
||||||
|
Subsequent sections don't explicitly callout the key addition to APIs. We assume that you will append the correct keys as per our explanation above.
|
||||||
|
|
||||||
Examples (please read security notice above)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Please remember, if you are using authentication, please add a ``-b cookies.txt`` to each of the commands below if you are using
|
|
||||||
CuRL. If you are not using CuRL and writing your own app, you need to make sure you pass on cookies to subsequent requests
|
|
||||||
in your app.
|
|
||||||
|
|
||||||
|
Examples
|
||||||
|
^^^^^^^^^
|
||||||
|
|
||||||
(In all examples, replace 'server' with IP or hostname & port where ZoneMinder is running)
|
(In all examples, replace 'server' with IP or hostname & port where ZoneMinder is running)
|
||||||
|
|
||||||
|
@ -410,6 +412,15 @@ This returns number of events per monitor that were recorded in the last day whe
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Return sorted events
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
This returns a list of events within a time range and also sorts it by descending order
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
curl -XGET "http://server/zm/api/events/index/StartTime%20>=:2015-05-15%2018:43:56/EndTime%20<=:208:43:56.json?sort=StartTime&direction=desc"
|
||||||
|
|
||||||
|
|
||||||
Configuration Apis
|
Configuration Apis
|
||||||
^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
@ -584,9 +595,104 @@ Returns:
|
||||||
|
|
||||||
This only works if you have a multiserver setup in place. If you don't it will return an empty array.
|
This only works if you have a multiserver setup in place. If you don't it will return an empty array.
|
||||||
|
|
||||||
|
Other APIs
|
||||||
|
^^^^^^^^^^
|
||||||
|
This is not a complete list. ZM supports more parameters/APIs. A good way to dive in is to look at the `API code <https://github.com/ZoneMinder/zoneminder/tree/master/web/api/app/Controller>`__ directly.
|
||||||
|
|
||||||
|
Streaming Interface
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
Developers working on their application often ask if there is an "API" to receive live streams, or recorded event streams.
|
||||||
|
It is possible to stream both live and recorded streams. This isn't strictly an "API" per-se (that is, it is not integrated
|
||||||
|
into the Cake PHP based API layer discussed here) and also why we've used the term "Interface" instead of an "API".
|
||||||
|
|
||||||
|
Live Streams
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
What you need to know is that if you want to display "live streams", ZoneMinder sends you streaming JPEG images (MJPEG)
|
||||||
|
which can easily be rendered in a browser using an ``img src`` tag.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
<img src="https://yourserver/zm/cgi-bin/nph-zms?scale=50&width=640p&height=480px&mode=jpeg&maxfps=5&buffer=1000&&monitor=1&token=eW<deleted>03&connkey=36139" />
|
||||||
|
|
||||||
|
# or
|
||||||
|
|
||||||
|
<img src="https://yourserver/zm/cgi-bin/nph-zms?scale=50&width=640p&height=480px&mode=jpeg&maxfps=5&buffer=1000&&monitor=1&auth=b5<deleted>03&connkey=36139" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
will display a live feed from monitor id 1, scaled down by 50% in quality and resized to 640x480px.
|
||||||
|
|
||||||
|
* This assumes ``/zm/cgi-bin`` is your CGI_BIN path. Change it to what is correct in your system
|
||||||
|
* The "auth" token you see above is required if you use ZoneMinder authentication. To understand how to get the auth token, please read the "Login, Logout & API security" section below.
|
||||||
|
* The "connkey" parameter is essentially a random number which uniquely identifies a stream. If you don't specify a connkey, ZM will generate its own. It is recommended to generate a connkey because you can then use it to "control" the stream (pause/resume etc.)
|
||||||
|
* Instead of dealing with the "auth" token, you can also use ``&user=username&pass=password`` where "username" and "password" are your ZoneMinder username and password respectively. Note that this is not recommended because you are transmitting them in a URL and even if you use HTTPS, they may show up in web server logs.
|
||||||
|
|
||||||
|
|
||||||
|
PTZ on live streams
|
||||||
|
-------------------
|
||||||
|
PTZ commands are pretty cryptic in ZoneMinder. This is not meant to be an exhaustive guide, but just something to whet your appetite:
|
||||||
|
|
||||||
|
|
||||||
|
Lets assume you have a monitor, with ID=6. Let's further assume you want to pan it left.
|
||||||
|
|
||||||
|
You'd need to send a:
|
||||||
|
``POST`` command to ``https://yourserver/zm/index.php`` with the following data payload in the command (NOT in the URL)
|
||||||
|
|
||||||
|
``view=request&request=control&id=6&control=moveConLeft&xge=30&yge=30``
|
||||||
|
|
||||||
|
Obviously, if you are using authentication, you need to be logged in for this to work.
|
||||||
|
|
||||||
|
Like I said, at this stage, this is only meant to get you started. Explore the ZoneMinder code and use "Inspect source" as you use PTZ commands in the ZoneMinder source code.
|
||||||
|
`control_functions.php <https://github.com/ZoneMinder/zoneminder/blob/10531df54312f52f0f32adec3d4720c063897b62/web/skins/classic/includes/control_functions.php>`__ is a great place to start.
|
||||||
|
|
||||||
|
|
||||||
|
Pre-recorded (past event) streams
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Similar to live playback, if you have chosen to store events in JPEG mode, you can play it back using:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
<img src="https://yourserver/zm/cgi-bin/nph-zms?mode=jpeg&frame=1&replay=none&source=event&event=293820&connkey=77493&token=ew<deleted>" />
|
||||||
|
|
||||||
|
# or
|
||||||
|
|
||||||
|
<img src="https://yourserver/zm/cgi-bin/nph-zms?mode=jpeg&frame=1&replay=none&source=event&event=293820&connkey=77493&auth=b5<deleted>" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* This assumes ``/zm/cgi-bin`` is your CGI_BIN path. Change it to what is correct in your system
|
||||||
|
* This will playback event 293820, starting from frame 1 as an MJPEG stream
|
||||||
|
* Like before, you can add more parameters like ``scale`` etc.
|
||||||
|
* auth and connkey have the same meaning as before, and yes, you can replace auth by ``&user=usename&pass=password`` as before and the same security concerns cited above apply.
|
||||||
|
|
||||||
|
If instead, you have chosen to use the MP4 (Video) storage mode for events, you can directly play back the saved video file:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
|
||||||
|
<video src="https://yourserver/zm/index.php?view=view_video&eid=294690&token=eW<deleted>" type="video/mp4"></video>
|
||||||
|
|
||||||
|
# or
|
||||||
|
|
||||||
|
<video src="https://yourserver/zm/index.php?view=view_video&eid=294690&auth=33<deleted>" type="video/mp4"></video>
|
||||||
|
|
||||||
|
|
||||||
|
This above will play back the video recording for event 294690
|
||||||
|
|
||||||
|
What other parameters are supported?
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
The best way to answer this question is to play with ZoneMinder console. Open a browser, play back live or recorded feed, and do an "Inspect Source" to see what parameters
|
||||||
|
are generated. Change and observe.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Further Reading
|
Further Reading
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
As described earlier, treat this document as an "introduction" to the important parts of the API and streaming interfaces.
|
As described earlier, treat this document as an "introduction" to the important parts of the API and streaming interfaces.
|
||||||
There are several details that haven't yet been documented. Till they are, here are some resources:
|
There are several details that haven't yet been documented. Till they are, here are some resources:
|
||||||
|
|
||||||
|
|
19
docs/faq.rst
19
docs/faq.rst
|
@ -646,6 +646,23 @@ Why am I getting broken images when trying to view events?
|
||||||
Zoneminder and the Apache web server need to have the right permissions. Check this forum topic and similar ones:
|
Zoneminder and the Apache web server need to have the right permissions. Check this forum topic and similar ones:
|
||||||
http://www.zoneminder.com/forums/viewtopic.php?p=48754#48754
|
http://www.zoneminder.com/forums/viewtopic.php?p=48754#48754
|
||||||
|
|
||||||
|
|
||||||
|
I can review events for the current day, but ones from yesterday and beyond error out
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
If you've checked that the `www-data` user has permissions to the storage folders, perhaps your php.ini's timezone setting is incorrect. They _must_ match for certain playback functions.
|
||||||
|
|
||||||
|
If you're using Linux, this can be found using the following command: ::
|
||||||
|
|
||||||
|
timedatectl | grep "Time zone"
|
||||||
|
|
||||||
|
If using FreeBSD, you can use this one-liner: ::
|
||||||
|
|
||||||
|
cd /usr/share/zoneinfo/ && find * -type f -exec cmp -s {} /etc/localtime \; -print;
|
||||||
|
|
||||||
|
Once you know what timezone your system is set to, open `/etc/php.ini` and adjust ``date.timezone`` to the appropriate value. the PHP daemon may need to be restarted for changes to take effect.
|
||||||
|
|
||||||
|
|
||||||
Why is the image from my color camera appearing in black and white?
|
Why is the image from my color camera appearing in black and white?
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
If you recently upgraded to zoneminder 1.26, there is a per camera option that defaults to black and white and can be mis-set if your upgrade didn't happen right. See this thread: http://www.zoneminder.com/forums/viewtopic.php?f=30&t=21344
|
If you recently upgraded to zoneminder 1.26, there is a per camera option that defaults to black and white and can be mis-set if your upgrade didn't happen right. See this thread: http://www.zoneminder.com/forums/viewtopic.php?f=30&t=21344
|
||||||
|
@ -728,6 +745,8 @@ What causes "Invalid JPEG file structure: two SOI markers" from zmc (1.24.x)
|
||||||
|
|
||||||
Some settings that used to be global only are now per camera. On the Monitor Source tab, if you are using Remote Protocol "HTTP" and Remote Method "Simple", try changing Remote Method to "Regexp".
|
Some settings that used to be global only are now per camera. On the Monitor Source tab, if you are using Remote Protocol "HTTP" and Remote Method "Simple", try changing Remote Method to "Regexp".
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Miscellaneous
|
Miscellaneous
|
||||||
-------------------
|
-------------------
|
||||||
I see ZoneMinder is licensed under the GPL. What does that allow or restrict me in doing with ZoneMinder?
|
I see ZoneMinder is licensed under the GPL. What does that allow or restrict me in doing with ZoneMinder?
|
||||||
|
|
|
@ -82,8 +82,7 @@ a read.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
gunzip /usr/share/doc/zoneminder/README.Debian.gz
|
zcat /usr/share/doc/zoneminder/README.Debian.gz
|
||||||
cat /usr/share/doc/zoneminder/README.Debian
|
|
||||||
|
|
||||||
|
|
||||||
**Step 7:** Enable ZoneMinder service
|
**Step 7:** Enable ZoneMinder service
|
||||||
|
@ -190,11 +189,17 @@ Add the following to the bottom of the file
|
||||||
::
|
::
|
||||||
|
|
||||||
# Backports repository
|
# Backports repository
|
||||||
deb http://httpredir.debian.org/debian jessie-backports main contrib non-free
|
deb http://archive.debian.org/debian/ jessie-backports main contrib non-free
|
||||||
|
|
||||||
CTRL+o and <Enter> to save
|
CTRL+o and <Enter> to save
|
||||||
CTRL+x to exit
|
CTRL+x to exit
|
||||||
|
|
||||||
|
Run the following
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
echo 'Acquire::Check-Valid-Until no;' > /etc/apt/apt.conf.d/99no-check-valid-until
|
||||||
|
|
||||||
**Step 5:** Install ZoneMinder
|
**Step 5:** Install ZoneMinder
|
||||||
|
|
||||||
::
|
::
|
||||||
|
@ -209,8 +214,7 @@ a read.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
gunzip /usr/share/doc/zoneminder/README.Debian.gz
|
zcat /usr/share/doc/zoneminder/README.Debian.gz
|
||||||
cat /usr/share/doc/zoneminder/README.Debian
|
|
||||||
|
|
||||||
**Step 7:** Setup Database
|
**Step 7:** Setup Database
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ configure_file(com.zoneminder.systemctl.rules.in "${CMAKE_CURRENT_BINARY_DIR}/co
|
||||||
configure_file(zoneminder.service.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder.service" @ONLY)
|
configure_file(zoneminder.service.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder.service" @ONLY)
|
||||||
configure_file(zoneminder-tmpfiles.conf.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder-tmpfiles.conf" @ONLY)
|
configure_file(zoneminder-tmpfiles.conf.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder-tmpfiles.conf" @ONLY)
|
||||||
configure_file(zoneminder.desktop.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder.desktop" @ONLY)
|
configure_file(zoneminder.desktop.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder.desktop" @ONLY)
|
||||||
|
configure_file(zm-sudo.in "${CMAKE_CURRENT_BINARY_DIR}/zm-sudo" @ONLY)
|
||||||
|
|
||||||
# Do not install the misc files by default
|
# Do not install the misc files by default
|
||||||
#install(FILES "${CMAKE_CURRENT_BINARY_DIR}/apache.conf" "${CMAKE_CURRENT_BINARY_DIR}/logrotate.conf" "${CMAKE_CURRENT_BINARY_DIR}/syslog.conf" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/misc")
|
#install(FILES "${CMAKE_CURRENT_BINARY_DIR}/apache.conf" "${CMAKE_CURRENT_BINARY_DIR}/logrotate.conf" "${CMAKE_CURRENT_BINARY_DIR}/syslog.conf" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/misc")
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
@WEB_USER@ ALL=NOPASSWD: @SBINDDIR@/shutdown
|
|
@ -396,6 +396,17 @@ our @options = (
|
||||||
type => $types{boolean},
|
type => $types{boolean},
|
||||||
category => 'system',
|
category => 'system',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name => 'ZM_OPT_USE_LEGACY_API_AUTH',
|
||||||
|
default => 'yes',
|
||||||
|
description => 'Enable legacy API authentication',
|
||||||
|
help => q`
|
||||||
|
Starting version 1.34.0, ZoneMinder uses a more secure
|
||||||
|
Authentication mechanism using JWT tokens. Older versions used a less secure MD5 based auth hash. It is recommended you turn this off after you are sure you don't need it. If you are using a 3rd party app that relies on the older API auth mechanisms, you will have to update that app if you turn this off. Note that zmNinja 1.3.057 onwards supports the new token system
|
||||||
|
`,
|
||||||
|
type => $types{boolean},
|
||||||
|
category => 'system',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name => 'ZM_OPT_USE_EVENTNOTIFICATION',
|
name => 'ZM_OPT_USE_EVENTNOTIFICATION',
|
||||||
default => 'no',
|
default => 'no',
|
||||||
|
@ -2560,17 +2571,23 @@ our @options = (
|
||||||
period of time (the section length). However in Mocord mode it
|
period of time (the section length). However in Mocord mode it
|
||||||
is possible that motion detection may occur near the end of a
|
is possible that motion detection may occur near the end of a
|
||||||
section. This option controls what happens when an alarm occurs
|
section. This option controls what happens when an alarm occurs
|
||||||
in Mocord mode. The 'time' setting means that the event will be
|
in Mocord mode.~~
|
||||||
closed at the end of the section regardless of alarm activity.
|
~~
|
||||||
|
The 'time' setting means that the event will be
|
||||||
|
closed at the end of the section regardless of alarm activity.~~
|
||||||
|
~~
|
||||||
The 'idle' setting means that the event will be closed at the
|
The 'idle' setting means that the event will be closed at the
|
||||||
end of the section if there is no alarm activity occurring at
|
end of the section if there is no alarm activity occurring at
|
||||||
the time otherwise it will be closed once the alarm is over
|
the time otherwise it will be closed once the alarm is over
|
||||||
meaning the event may end up being longer than the normal
|
meaning the event may end up being longer than the normal
|
||||||
section length. The 'alarm' setting means that if an alarm
|
section length.~~
|
||||||
occurs during the event, the event will be closed once the
|
~~
|
||||||
alarm is over regardless of when this occurs. This has the
|
The 'alarm' setting means that if an alarm
|
||||||
|
occurs during the event, the event will be closed and a new one
|
||||||
|
will be opened. So events will only be alarmed or continuous.
|
||||||
|
This has the
|
||||||
effect of limiting the number of alarms to one per event and
|
effect of limiting the number of alarms to one per event and
|
||||||
the events will be shorter than the section length if an alarm
|
the events may be shorter than the section length if an alarm
|
||||||
has occurred.
|
has occurred.
|
||||||
`,
|
`,
|
||||||
type => $types{boolean},
|
type => $types{boolean},
|
||||||
|
@ -3957,7 +3974,15 @@ our @options = (
|
||||||
help => q`This will affect how long a session will be valid for since the last request. Keeping this short helps prevent session hijacking. Keeping it long allows you to stay logged in longer without refreshing the view.`,
|
help => q`This will affect how long a session will be valid for since the last request. Keeping this short helps prevent session hijacking. Keeping it long allows you to stay logged in longer without refreshing the view.`,
|
||||||
type => $types{integer},
|
type => $types{integer},
|
||||||
category => 'system',
|
category => 'system',
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
name => 'ZM_RECORD_DIAG_IMAGES_FIFO',
|
||||||
|
default => 'no',
|
||||||
|
description => ' Recording intermediate alarm diagnostic use fifo instead of files (faster)',
|
||||||
|
help => 'This tries to lessen the load of recording diag images by sending them to a memory FIFO pipe instead of creating each file.',
|
||||||
|
type => $types{boolean},
|
||||||
|
category => 'logging',
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
our %options_hash = map { ( $_->{name}, $_ ) } @options;
|
our %options_hash = map { ( $_->{name}, $_ ) } @options;
|
||||||
|
|
|
@ -96,8 +96,7 @@ sub open {
|
||||||
$self->{state} = 'open';
|
$self->{state} = 'open';
|
||||||
}
|
}
|
||||||
|
|
||||||
sub parseControlAddress
|
sub parseControlAddress {
|
||||||
{
|
|
||||||
my $controlAddress = shift;
|
my $controlAddress = shift;
|
||||||
my ($usernamepassword, $addressport) = split /@/, $controlAddress;
|
my ($usernamepassword, $addressport) = split /@/, $controlAddress;
|
||||||
if ( !defined $addressport ) {
|
if ( !defined $addressport ) {
|
||||||
|
@ -105,7 +104,7 @@ sub parseControlAddress
|
||||||
$addressport = $usernamepassword;
|
$addressport = $usernamepassword;
|
||||||
} else {
|
} else {
|
||||||
my ($username , $password) = split /:/, $usernamepassword;
|
my ($username , $password) = split /:/, $usernamepassword;
|
||||||
%identity = (username => "$username", password => "$password");
|
%identity = (username => $username, password => $password);
|
||||||
}
|
}
|
||||||
($address, $port) = split /:/, $addressport;
|
($address, $port) = split /:/, $addressport;
|
||||||
}
|
}
|
||||||
|
@ -118,12 +117,11 @@ sub digestBase64
|
||||||
return encode_base64($shaGenerator->digest, "");
|
return encode_base64($shaGenerator->digest, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
sub authentificationHeader
|
sub authentificationHeader {
|
||||||
{
|
|
||||||
my ($username, $password) = @_;
|
my ($username, $password) = @_;
|
||||||
my $nonce;
|
my $nonce;
|
||||||
$nonce .= chr(int(rand(254))) for (0 .. 20);
|
$nonce .= chr(int(rand(254))) for (0 .. 20);
|
||||||
my $nonceBase64 = encode_base64($nonce, "");
|
my $nonceBase64 = encode_base64($nonce, '');
|
||||||
my $currentDate = DateTime->now()->iso8601().'Z';
|
my $currentDate = DateTime->now()->iso8601().'Z';
|
||||||
|
|
||||||
return '<s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><Username>' . $username . '</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">' . digestBase64($nonce, $currentDate, $password) . '</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">' . $nonceBase64 . '</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">' . $currentDate . '</Created></UsernameToken></Security></s:Header>';
|
return '<s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><Username>' . $username . '</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">' . digestBase64($nonce, $currentDate, $password) . '</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">' . $nonceBase64 . '</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">' . $currentDate . '</Created></UsernameToken></Security></s:Header>';
|
||||||
|
@ -160,7 +158,7 @@ sub sendCmd {
|
||||||
if ( $res->is_success ) {
|
if ( $res->is_success ) {
|
||||||
$result = !undef;
|
$result = !undef;
|
||||||
} else {
|
} else {
|
||||||
Error("After sending PTZ command, camera returned the following error:'".$res->status_line()."'");
|
Error("After sending PTZ command, camera returned the following error:'".$res->status_line()."'\nMSG:$msg\nResponse:".$res->content);
|
||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
@ -236,7 +234,7 @@ sub moveConDown {
|
||||||
Debug('Move Down');
|
Debug('Move Down');
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my $cmd = 'onvif/PTZ';
|
my $cmd = 'onvif/PTZ';
|
||||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>' . $profileToken . '</ProfileToken><Velocity><PanTilt x="0" y="-0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">'.((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '').'<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>' . $profileToken . '</ProfileToken><Velocity><PanTilt x="0" y="-0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||||
$self->sendCmd($cmd, $msg, $content_type);
|
$self->sendCmd($cmd, $msg, $content_type);
|
||||||
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
|
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
|
||||||
|
@ -316,7 +314,7 @@ sub moveConUpLeft {
|
||||||
Debug('Move Diagonally Up Left');
|
Debug('Move Diagonally Up Left');
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my $cmd = 'onvif/PTZ';
|
my $cmd = 'onvif/PTZ';
|
||||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>' . $profileToken . '</ProfileToken><Velocity><PanTilt x="-0.5" y="0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">'.((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '').'<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>' . $profileToken . '</ProfileToken><Velocity><PanTilt x="-0.5" y="0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||||
$self->sendCmd($cmd, $msg, $content_type);
|
$self->sendCmd($cmd, $msg, $content_type);
|
||||||
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
|
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
|
||||||
|
|
|
@ -214,6 +214,7 @@ sub zmDbGetMonitor {
|
||||||
return undef;
|
return undef;
|
||||||
}
|
}
|
||||||
my $monitor = $sth->fetchrow_hashref();
|
my $monitor = $sth->fetchrow_hashref();
|
||||||
|
$sth->finish();
|
||||||
return $monitor;
|
return $monitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,6 +241,7 @@ sub zmDbGetMonitorAndControl {
|
||||||
return undef;
|
return undef;
|
||||||
}
|
}
|
||||||
my $monitor = $sth->fetchrow_hashref();
|
my $monitor = $sth->fetchrow_hashref();
|
||||||
|
$sth->finish();
|
||||||
return $monitor;
|
return $monitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ require Number::Bytes::Human;
|
||||||
require Date::Parse;
|
require Date::Parse;
|
||||||
require POSIX;
|
require POSIX;
|
||||||
use Date::Format qw(time2str);
|
use Date::Format qw(time2str);
|
||||||
|
use Time::HiRes qw(gettimeofday tv_interval);
|
||||||
|
|
||||||
#our @ISA = qw(ZoneMinder::Object);
|
#our @ISA = qw(ZoneMinder::Object);
|
||||||
use parent qw(ZoneMinder::Object);
|
use parent qw(ZoneMinder::Object);
|
||||||
|
@ -63,6 +64,7 @@ $serial = $primary_key = 'Id';
|
||||||
Id
|
Id
|
||||||
MonitorId
|
MonitorId
|
||||||
StorageId
|
StorageId
|
||||||
|
SecondaryStorageId
|
||||||
Name
|
Name
|
||||||
Cause
|
Cause
|
||||||
StartTime
|
StartTime
|
||||||
|
@ -116,7 +118,7 @@ sub Time {
|
||||||
}
|
}
|
||||||
|
|
||||||
sub getPath {
|
sub getPath {
|
||||||
return Path( @_ );
|
return Path(@_);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub Path {
|
sub Path {
|
||||||
|
@ -131,7 +133,7 @@ sub Path {
|
||||||
|
|
||||||
if ( ! $$event{Path} ) {
|
if ( ! $$event{Path} ) {
|
||||||
my $Storage = $event->Storage();
|
my $Storage = $event->Storage();
|
||||||
$$event{Path} = join('/', $Storage->Path(), $event->RelativePath() );
|
$$event{Path} = join('/', $Storage->Path(), $event->RelativePath());
|
||||||
}
|
}
|
||||||
return $$event{Path};
|
return $$event{Path};
|
||||||
}
|
}
|
||||||
|
@ -163,7 +165,8 @@ sub RelativePath {
|
||||||
if ( $event->Time() ) {
|
if ( $event->Time() ) {
|
||||||
$$event{RelativePath} = join('/',
|
$$event{RelativePath} = join('/',
|
||||||
$event->{MonitorId},
|
$event->{MonitorId},
|
||||||
POSIX::strftime( '%y/%m/%d/%H/%M/%S',
|
POSIX::strftime(
|
||||||
|
'%y/%m/%d/%H/%M/%S',
|
||||||
localtime($event->Time())
|
localtime($event->Time())
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -203,7 +206,8 @@ sub LinkPath {
|
||||||
if ( $event->Time() ) {
|
if ( $event->Time() ) {
|
||||||
$$event{LinkPath} = join('/',
|
$$event{LinkPath} = join('/',
|
||||||
$event->{MonitorId},
|
$event->{MonitorId},
|
||||||
POSIX::strftime( '%y/%m/%d',
|
POSIX::strftime(
|
||||||
|
'%y/%m/%d',
|
||||||
localtime($event->Time())
|
localtime($event->Time())
|
||||||
),
|
),
|
||||||
'.'.$$event{Id}
|
'.'.$$event{Id}
|
||||||
|
@ -255,8 +259,8 @@ sub createIdFile {
|
||||||
sub GenerateVideo {
|
sub GenerateVideo {
|
||||||
my ( $self, $rate, $fps, $scale, $size, $overwrite, $format ) = @_;
|
my ( $self, $rate, $fps, $scale, $size, $overwrite, $format ) = @_;
|
||||||
|
|
||||||
my $event_path = $self->Path( );
|
my $event_path = $self->Path();
|
||||||
chdir( $event_path );
|
chdir($event_path);
|
||||||
( my $video_name = $self->{Name} ) =~ s/\s/_/g;
|
( my $video_name = $self->{Name} ) =~ s/\s/_/g;
|
||||||
|
|
||||||
my @file_parts;
|
my @file_parts;
|
||||||
|
@ -282,10 +286,10 @@ sub GenerateVideo {
|
||||||
$file_scale =~ s/_00//;
|
$file_scale =~ s/_00//;
|
||||||
$file_scale =~ s/(_\d+)0+$/$1/;
|
$file_scale =~ s/(_\d+)0+$/$1/;
|
||||||
$file_scale = 's'.$file_scale;
|
$file_scale = 's'.$file_scale;
|
||||||
push( @file_parts, $file_scale );
|
push @file_parts, $file_scale;
|
||||||
} elsif ( $size ) {
|
} elsif ( $size ) {
|
||||||
my $file_size = 'S'.$size;
|
my $file_size = 'S'.$size;
|
||||||
push( @file_parts, $file_size );
|
push @file_parts, $file_size;
|
||||||
}
|
}
|
||||||
my $video_file = join('-', $video_name, $file_parts[0], $file_parts[1] ).'.'.$format;
|
my $video_file = join('-', $video_name, $file_parts[0], $file_parts[1] ).'.'.$format;
|
||||||
if ( $overwrite || !-s $video_file ) {
|
if ( $overwrite || !-s $video_file ) {
|
||||||
|
@ -393,61 +397,66 @@ sub delete {
|
||||||
sub delete_files {
|
sub delete_files {
|
||||||
my $event = shift;
|
my $event = shift;
|
||||||
|
|
||||||
my $Storage = @_ ? $_[0] : new ZoneMinder::Storage($$event{StorageId});
|
foreach my $Storage (
|
||||||
my $storage_path = $Storage->Path();
|
@_ ? ($_[0]) : (
|
||||||
|
new ZoneMinder::Storage($$event{StorageId}),
|
||||||
|
( $$event{SecondaryStorageId} ? new ZoneMinder::Storage($$event{SecondaryStorageId}) : () ),
|
||||||
|
) ) {
|
||||||
|
my $storage_path = $Storage->Path();
|
||||||
|
|
||||||
if ( ! $storage_path ) {
|
if ( ! $storage_path ) {
|
||||||
Error("Empty storage path when deleting files for event $$event{Id} with storage id $$event{StorageId}");
|
Error("Empty storage path when deleting files for event $$event{Id} with storage id $$event{StorageId}");
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! $$event{MonitorId} ) {
|
|
||||||
Error("No monitor id assigned to event $$event{Id}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
my $event_path = $event->RelativePath();
|
|
||||||
Debug("Deleting files for Event $$event{Id} from $storage_path/$event_path, scheme is $$event{Scheme}.");
|
|
||||||
if ( $event_path ) {
|
|
||||||
( $storage_path ) = ( $storage_path =~ /^(.*)$/ ); # De-taint
|
|
||||||
( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint
|
|
||||||
|
|
||||||
my $deleted = 0;
|
|
||||||
if ( $$Storage{Type} and ( $$Storage{Type} eq 's3fs' ) ) {
|
|
||||||
my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$Storage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ );
|
|
||||||
eval {
|
|
||||||
require Net::Amazon::S3;
|
|
||||||
my $s3 = Net::Amazon::S3->new( {
|
|
||||||
aws_access_key_id => $aws_id,
|
|
||||||
aws_secret_access_key => $aws_secret,
|
|
||||||
( $aws_host ? ( host => $aws_host ) : () ),
|
|
||||||
});
|
|
||||||
my $bucket = $s3->bucket($aws_bucket);
|
|
||||||
if ( ! $bucket ) {
|
|
||||||
Error("S3 bucket $bucket not found.");
|
|
||||||
die;
|
|
||||||
}
|
|
||||||
if ( $bucket->delete_key($event_path) ) {
|
|
||||||
$deleted = 1;
|
|
||||||
} else {
|
|
||||||
Error('Failed to delete from S3:'.$s3->err . ': ' . $s3->errstr);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Error($@) if $@;
|
|
||||||
}
|
}
|
||||||
if ( !$deleted ) {
|
|
||||||
my $command = "/bin/rm -rf $storage_path/$event_path";
|
|
||||||
ZoneMinder::General::executeShellCommand($command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( $event->Scheme() eq 'Deep' ) {
|
if ( ! $$event{MonitorId} ) {
|
||||||
my $link_path = $event->LinkPath();
|
Error("No monitor id assigned to event $$event{Id}");
|
||||||
Debug("Deleting link for Event $$event{Id} from $storage_path/$link_path.");
|
return;
|
||||||
if ( $link_path ) {
|
}
|
||||||
( $link_path ) = ( $link_path =~ /^(.*)$/ ); # De-taint
|
my $event_path = $event->RelativePath();
|
||||||
|
Debug("Deleting files for Event $$event{Id} from $storage_path/$event_path, scheme is $$event{Scheme}.");
|
||||||
|
if ( $event_path ) {
|
||||||
|
( $storage_path ) = ( $storage_path =~ /^(.*)$/ ); # De-taint
|
||||||
|
( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint
|
||||||
|
|
||||||
|
my $deleted = 0;
|
||||||
|
if ( $$Storage{Type} and ( $$Storage{Type} eq 's3fs' ) ) {
|
||||||
|
my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$Storage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ );
|
||||||
|
eval {
|
||||||
|
require Net::Amazon::S3;
|
||||||
|
my $s3 = Net::Amazon::S3->new( {
|
||||||
|
aws_access_key_id => $aws_id,
|
||||||
|
aws_secret_access_key => $aws_secret,
|
||||||
|
( $aws_host ? ( host => $aws_host ) : () ),
|
||||||
|
});
|
||||||
|
my $bucket = $s3->bucket($aws_bucket);
|
||||||
|
if ( ! $bucket ) {
|
||||||
|
Error("S3 bucket $bucket not found.");
|
||||||
|
die;
|
||||||
|
}
|
||||||
|
if ( $bucket->delete_key($event_path) ) {
|
||||||
|
$deleted = 1;
|
||||||
|
} else {
|
||||||
|
Error('Failed to delete from S3:'.$s3->err . ': ' . $s3->errstr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Error($@) if $@;
|
||||||
|
}
|
||||||
|
if ( !$deleted ) {
|
||||||
|
my $command = "/bin/rm -rf $storage_path/$event_path";
|
||||||
|
ZoneMinder::General::executeShellCommand($command);
|
||||||
|
}
|
||||||
|
} # end if event_path
|
||||||
|
|
||||||
|
if ( $event->Scheme() eq 'Deep' ) {
|
||||||
|
my $link_path = $event->LinkPath();
|
||||||
|
Debug("Deleting link for Event $$event{Id} from $storage_path/$link_path.");
|
||||||
|
if ( $link_path ) {
|
||||||
|
( $link_path ) = ( $link_path =~ /^(.*)$/ ); # De-taint
|
||||||
unlink($storage_path.'/'.$link_path) or Error("Unable to unlink '$storage_path/$link_path': $!");
|
unlink($storage_path.'/'.$link_path) or Error("Unable to unlink '$storage_path/$link_path': $!");
|
||||||
}
|
}
|
||||||
}
|
} # end if Scheme eq Deep
|
||||||
|
} # end foreach Storage
|
||||||
} # end sub delete_files
|
} # end sub delete_files
|
||||||
|
|
||||||
sub StorageId {
|
sub StorageId {
|
||||||
|
@ -519,7 +528,7 @@ sub DiskSpace {
|
||||||
return $_[0]{DiskSpace};
|
return $_[0]{DiskSpace};
|
||||||
}
|
}
|
||||||
|
|
||||||
sub MoveTo {
|
sub CopyTo {
|
||||||
my ( $self, $NewStorage ) = @_;
|
my ( $self, $NewStorage ) = @_;
|
||||||
|
|
||||||
my $OldStorage = $self->Storage(undef);
|
my $OldStorage = $self->Storage(undef);
|
||||||
|
@ -531,9 +540,9 @@ sub MoveTo {
|
||||||
# We do this before bothering to lock the event
|
# We do this before bothering to lock the event
|
||||||
my ( $NewPath ) = ( $NewStorage->Path() =~ /^(.*)$/ ); # De-taint
|
my ( $NewPath ) = ( $NewStorage->Path() =~ /^(.*)$/ ); # De-taint
|
||||||
if ( ! $$NewStorage{Id} ) {
|
if ( ! $$NewStorage{Id} ) {
|
||||||
return "New storage does not have an id. Moving will not happen.";
|
return 'New storage does not have an id. Moving will not happen.';
|
||||||
} elsif ( $$NewStorage{Id} == $$self{StorageId} ) {
|
} elsif ( $$NewStorage{Id} == $$self{StorageId} ) {
|
||||||
return "Event is already located at " . $NewPath;
|
return 'Event is already located at ' . $NewPath;
|
||||||
} elsif ( !$NewPath ) {
|
} elsif ( !$NewPath ) {
|
||||||
return "New path ($NewPath) is empty.";
|
return "New path ($NewPath) is empty.";
|
||||||
} elsif ( ! -e $NewPath ) {
|
} elsif ( ! -e $NewPath ) {
|
||||||
|
@ -545,7 +554,7 @@ sub MoveTo {
|
||||||
# data is reloaded, so need to check that the move hasn't already happened.
|
# data is reloaded, so need to check that the move hasn't already happened.
|
||||||
if ( $$self{StorageId} == $$NewStorage{Id} ) {
|
if ( $$self{StorageId} == $$NewStorage{Id} ) {
|
||||||
$ZoneMinder::Database::dbh->commit();
|
$ZoneMinder::Database::dbh->commit();
|
||||||
return "Event has already been moved by someone else.";
|
return 'Event has already been moved by someone else.';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $$OldStorage{Id} != $$self{StorageId} ) {
|
if ( $$OldStorage{Id} != $$self{StorageId} ) {
|
||||||
|
@ -553,76 +562,82 @@ sub MoveTo {
|
||||||
return 'Old Storage path changed, Event has moved somewhere else.';
|
return 'Old Storage path changed, Event has moved somewhere else.';
|
||||||
}
|
}
|
||||||
|
|
||||||
$$self{Storage} = $NewStorage;
|
$NewPath .= $self->Relative_Path();
|
||||||
( $NewPath ) = ( $self->Path(undef) =~ /^(.*)$/ ); # De-taint
|
$NewPath = ( $NewPath =~ /^(.*)$/ ); # De-taint
|
||||||
if ( $NewPath eq $OldPath ) {
|
if ( $NewPath eq $OldPath ) {
|
||||||
$ZoneMinder::Database::dbh->commit();
|
$ZoneMinder::Database::dbh->commit();
|
||||||
return "New path and old path are the same! $NewPath";
|
return "New path and old path are the same! $NewPath";
|
||||||
}
|
}
|
||||||
Debug("Moving event $$self{Id} from $OldPath to $NewPath");
|
Debug("Copying event $$self{Id} from $OldPath to $NewPath");
|
||||||
|
|
||||||
my $moved = 0;
|
my $moved = 0;
|
||||||
|
|
||||||
if ( $$NewStorage{Type} eq 's3fs' ) {
|
if ( $$NewStorage{Type} eq 's3fs' ) {
|
||||||
my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$NewStorage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ );
|
if ( $$NewStorage{Url} ) {
|
||||||
eval {
|
my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$NewStorage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ );
|
||||||
require Net::Amazon::S3;
|
if ( $aws_id and $aws_secret and $aws_host and $aws_bucket ) {
|
||||||
require File::Slurp;
|
eval {
|
||||||
my $s3 = Net::Amazon::S3->new( {
|
require Net::Amazon::S3;
|
||||||
aws_access_key_id => $aws_id,
|
require File::Slurp;
|
||||||
aws_secret_access_key => $aws_secret,
|
my $s3 = Net::Amazon::S3->new( {
|
||||||
( $aws_host ? ( host => $aws_host ) : () ),
|
aws_access_key_id => $aws_id,
|
||||||
});
|
aws_secret_access_key => $aws_secret,
|
||||||
my $bucket = $s3->bucket($aws_bucket);
|
( $aws_host ? ( host => $aws_host ) : () ),
|
||||||
if ( ! $bucket ) {
|
});
|
||||||
Error("S3 bucket $bucket not found.");
|
my $bucket = $s3->bucket($aws_bucket);
|
||||||
die;
|
if ( !$bucket ) {
|
||||||
|
Error("S3 bucket $bucket not found.");
|
||||||
|
die;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $event_path = $self->RelativePath();
|
||||||
|
Debug("Making directory $event_path/");
|
||||||
|
if ( ! $bucket->add_key($event_path.'/', '') ) {
|
||||||
|
die "Unable to add key for $event_path/";
|
||||||
|
}
|
||||||
|
|
||||||
|
my @files = glob("$OldPath/*");
|
||||||
|
Debug("Files to move @files");
|
||||||
|
foreach my $file ( @files ) {
|
||||||
|
next if $file =~ /^\./;
|
||||||
|
( $file ) = ( $file =~ /^(.*)$/ ); # De-taint
|
||||||
|
my $starttime = [gettimeofday];
|
||||||
|
Debug("Moving file $file to $NewPath");
|
||||||
|
my $size = -s $file;
|
||||||
|
if ( ! $size ) {
|
||||||
|
Info('Not moving file with 0 size');
|
||||||
|
}
|
||||||
|
my $file_contents = File::Slurp::read_file($file);
|
||||||
|
if ( ! $file_contents ) {
|
||||||
|
die 'Loaded empty file, but it had a size. Giving up';
|
||||||
|
}
|
||||||
|
|
||||||
|
my $filename = $event_path.'/'.File::Basename::basename($file);
|
||||||
|
if ( ! $bucket->add_key($filename, $file_contents) ) {
|
||||||
|
die "Unable to add key for $filename";
|
||||||
|
}
|
||||||
|
my $duration = tv_interval($starttime);
|
||||||
|
Debug('PUT to S3 ' . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . Number::Bytes::Human::format_bytes($duration?$size/$duration:$size) . '/sec');
|
||||||
|
} # end foreach file.
|
||||||
|
|
||||||
|
$moved = 1;
|
||||||
|
};
|
||||||
|
Error($@) if $@;
|
||||||
|
} else {
|
||||||
|
Error("Unable to parse S3 Url into it's component parts.");
|
||||||
}
|
}
|
||||||
|
#die $@ if $@;
|
||||||
my $event_path = 'events/'.$self->RelativePath();
|
} # end if Url
|
||||||
Info("Making dir ectory $event_path/");
|
|
||||||
if ( ! $bucket->add_key( $event_path.'/','' ) ) {
|
|
||||||
die "Unable to add key for $event_path/";
|
|
||||||
}
|
|
||||||
|
|
||||||
my @files = glob("$OldPath/*");
|
|
||||||
Debug("Files to move @files");
|
|
||||||
for my $file (@files) {
|
|
||||||
next if $file =~ /^\./;
|
|
||||||
( $file ) = ( $file =~ /^(.*)$/ ); # De-taint
|
|
||||||
my $starttime = time;
|
|
||||||
Debug("Moving file $file to $NewPath");
|
|
||||||
my $size = -s $file;
|
|
||||||
if ( ! $size ) {
|
|
||||||
Info('Not moving file with 0 size');
|
|
||||||
}
|
|
||||||
my $file_contents = File::Slurp::read_file($file);
|
|
||||||
if ( ! $file_contents ) {
|
|
||||||
die 'Loaded empty file, but it had a size. Giving up';
|
|
||||||
}
|
|
||||||
|
|
||||||
my $filename = $event_path.'/'.File::Basename::basename($file);
|
|
||||||
if ( ! $bucket->add_key( $filename, $file_contents ) ) {
|
|
||||||
die "Unable to add key for $filename";
|
|
||||||
}
|
|
||||||
my $duration = time - $starttime;
|
|
||||||
Debug('PUT to S3 ' . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . Number::Bytes::Human::format_bytes($duration?$size/$duration:$size) . '/sec');
|
|
||||||
} # end foreach file.
|
|
||||||
|
|
||||||
$moved = 1;
|
|
||||||
};
|
|
||||||
Error($@) if $@;
|
|
||||||
die $@ if $@;
|
|
||||||
} # end if s3
|
} # end if s3
|
||||||
|
|
||||||
my $error = '';
|
my $error = '';
|
||||||
if ( ! $moved ) {
|
if ( !$moved ) {
|
||||||
File::Path::make_path( $NewPath, {error => \my $err} );
|
File::Path::make_path($NewPath, {error => \my $err});
|
||||||
if ( @$err ) {
|
if ( @$err ) {
|
||||||
for my $diag (@$err) {
|
for my $diag (@$err) {
|
||||||
my ($file, $message) = %$diag;
|
my ($file, $message) = %$diag;
|
||||||
next if $message eq 'File exists';
|
next if $message eq 'File exists';
|
||||||
if ($file eq '') {
|
if ( $file eq '' ) {
|
||||||
$error .= "general error: $message\n";
|
$error .= "general error: $message\n";
|
||||||
} else {
|
} else {
|
||||||
$error .= "problem making $file: $message\n";
|
$error .= "problem making $file: $message\n";
|
||||||
|
@ -636,21 +651,21 @@ Debug("Files to move @files");
|
||||||
my @files = glob("$OldPath/*");
|
my @files = glob("$OldPath/*");
|
||||||
if ( ! @files ) {
|
if ( ! @files ) {
|
||||||
$ZoneMinder::Database::dbh->commit();
|
$ZoneMinder::Database::dbh->commit();
|
||||||
return "No files to move.";
|
return 'No files to move.';
|
||||||
}
|
}
|
||||||
|
|
||||||
for my $file (@files) {
|
for my $file (@files) {
|
||||||
next if $file =~ /^\./;
|
next if $file =~ /^\./;
|
||||||
( $file ) = ( $file =~ /^(.*)$/ ); # De-taint
|
( $file ) = ( $file =~ /^(.*)$/ ); # De-taint
|
||||||
my $starttime = time;
|
my $starttime = [gettimeofday];
|
||||||
Debug("Moving file $file to $NewPath");
|
Debug("Moving file $file to $NewPath");
|
||||||
my $size = -s $file;
|
my $size = -s $file;
|
||||||
if ( ! File::Copy::copy( $file, $NewPath ) ) {
|
if ( ! File::Copy::copy( $file, $NewPath ) ) {
|
||||||
$error .= "Copy failed: for $file to $NewPath: $!";
|
$error .= "Copy failed: for $file to $NewPath: $!";
|
||||||
last;
|
last;
|
||||||
}
|
}
|
||||||
my $duration = time - $starttime;
|
my $duration = tv_interval($starttime);
|
||||||
Debug("Copied " . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . ($duration?Number::Bytes::Human::format_bytes($size/$duration):'inf') . "/sec");
|
Debug('Copied ' . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . ($duration?Number::Bytes::Human::format_bytes($size/$duration):'inf') . '/sec');
|
||||||
} # end foreach file.
|
} # end foreach file.
|
||||||
} # end if ! moved
|
} # end if ! moved
|
||||||
|
|
||||||
|
@ -658,19 +673,26 @@ Debug("Files to move @files");
|
||||||
$ZoneMinder::Database::dbh->commit();
|
$ZoneMinder::Database::dbh->commit();
|
||||||
return $error;
|
return $error;
|
||||||
}
|
}
|
||||||
|
} # end sub CopyTo
|
||||||
|
|
||||||
|
sub MoveTo {
|
||||||
|
|
||||||
|
my ( $self, $NewStorage ) = @_;
|
||||||
|
my $OldStorage = $self->Storage(undef);
|
||||||
|
|
||||||
|
my $error = $self->CopyTo($NewStorage);
|
||||||
|
return $error if $error;
|
||||||
|
|
||||||
# Succeeded in copying all files, so we may now update the Event.
|
# Succeeded in copying all files, so we may now update the Event.
|
||||||
$$self{StorageId} = $$NewStorage{Id};
|
$$self{StorageId} = $$NewStorage{Id};
|
||||||
$$self{Storage} = $NewStorage;
|
$self->Storage($NewStorage);
|
||||||
$error .= $self->save();
|
$error .= $self->save();
|
||||||
if ( $error ) {
|
if ( $error ) {
|
||||||
$ZoneMinder::Database::dbh->commit();
|
$ZoneMinder::Database::dbh->commit();
|
||||||
return $error;
|
return $error;
|
||||||
}
|
}
|
||||||
Debug("Committing");
|
|
||||||
$ZoneMinder::Database::dbh->commit();
|
$ZoneMinder::Database::dbh->commit();
|
||||||
$self->delete_files( $OldStorage );
|
$self->delete_files($OldStorage);
|
||||||
Debug("Done deleting files, returning");
|
|
||||||
return $error;
|
return $error;
|
||||||
} # end sub MoveTo
|
} # end sub MoveTo
|
||||||
|
|
||||||
|
|
|
@ -72,15 +72,15 @@ sub find {
|
||||||
$sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters;
|
$sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters;
|
||||||
$sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit};
|
$sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit};
|
||||||
|
|
||||||
my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql )
|
my $sth = $ZoneMinder::Database::dbh->prepare_cached($sql)
|
||||||
or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() );
|
or Fatal("Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr());
|
||||||
my $res = $sth->execute( @sql_values )
|
my $res = $sth->execute(@sql_values)
|
||||||
or Fatal( "Can't execute '$sql': ".$sth->errstr() );
|
or Fatal("Can't execute '$sql': ".$sth->errstr());
|
||||||
|
|
||||||
my @results;
|
my @results;
|
||||||
|
|
||||||
while( my $db_filter = $sth->fetchrow_hashref() ) {
|
while( my $db_filter = $sth->fetchrow_hashref() ) {
|
||||||
my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter );
|
my $filter = new ZoneMinder::Filter($$db_filter{Id}, $db_filter);
|
||||||
push @results, $filter;
|
push @results, $filter;
|
||||||
} # end while
|
} # end while
|
||||||
$sth->finish();
|
$sth->finish();
|
||||||
|
@ -98,7 +98,7 @@ sub Execute {
|
||||||
my $sql = $self->Sql(undef);
|
my $sql = $self->Sql(undef);
|
||||||
|
|
||||||
if ( $self->{HasDiskPercent} ) {
|
if ( $self->{HasDiskPercent} ) {
|
||||||
my $disk_percent = getDiskPercent( $$self{Storage} ? $$self{Storage}->Path() : () );
|
my $disk_percent = getDiskPercent($$self{Storage} ? $$self{Storage}->Path() : ());
|
||||||
$sql =~ s/zmDiskPercent/$disk_percent/g;
|
$sql =~ s/zmDiskPercent/$disk_percent/g;
|
||||||
}
|
}
|
||||||
if ( $self->{HasDiskBlocks} ) {
|
if ( $self->{HasDiskBlocks} ) {
|
||||||
|
@ -111,16 +111,16 @@ sub Execute {
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug("Filter::Execute SQL ($sql)");
|
Debug("Filter::Execute SQL ($sql)");
|
||||||
my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql )
|
my $sth = $ZoneMinder::Database::dbh->prepare_cached($sql)
|
||||||
or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() );
|
or Fatal("Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr());
|
||||||
my $res = $sth->execute();
|
my $res = $sth->execute();
|
||||||
if ( !$res ) {
|
if ( !$res ) {
|
||||||
Error( "Can't execute filter '$sql', ignoring: ".$sth->errstr() );
|
Error("Can't execute filter '$sql', ignoring: ".$sth->errstr());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
my @results;
|
my @results;
|
||||||
|
|
||||||
while( my $event = $sth->fetchrow_hashref() ) {
|
while ( my $event = $sth->fetchrow_hashref() ) {
|
||||||
push @results, $event;
|
push @results, $event;
|
||||||
}
|
}
|
||||||
$sth->finish();
|
$sth->finish();
|
||||||
|
@ -132,7 +132,13 @@ sub Sql {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
$$self{Sql} = shift if @_;
|
$$self{Sql} = shift if @_;
|
||||||
if ( ! $$self{Sql} ) {
|
if ( ! $$self{Sql} ) {
|
||||||
my $filter_expr = ZoneMinder::General::jsonDecode($self->{Query});
|
$self->{Sql} = '';
|
||||||
|
if ( ! $self->{Query_json} ) {
|
||||||
|
Warning("No query in Filter!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $filter_expr = ZoneMinder::General::jsonDecode($self->{Query_json});
|
||||||
my $sql = 'SELECT E.*,
|
my $sql = 'SELECT E.*,
|
||||||
unix_timestamp(E.StartTime) as Time,
|
unix_timestamp(E.StartTime) as Time,
|
||||||
M.Name as MonitorName,
|
M.Name as MonitorName,
|
||||||
|
@ -142,7 +148,6 @@ sub Sql {
|
||||||
INNER JOIN Monitors as M on M.Id = E.MonitorId
|
INNER JOIN Monitors as M on M.Id = E.MonitorId
|
||||||
LEFT JOIN Storage as S on S.Id = E.StorageId
|
LEFT JOIN Storage as S on S.Id = E.StorageId
|
||||||
';
|
';
|
||||||
$self->{Sql} = '';
|
|
||||||
|
|
||||||
if ( $filter_expr->{terms} ) {
|
if ( $filter_expr->{terms} ) {
|
||||||
foreach my $term ( @{$filter_expr->{terms}} ) {
|
foreach my $term ( @{$filter_expr->{terms}} ) {
|
||||||
|
@ -152,7 +157,7 @@ sub Sql {
|
||||||
$self->{Sql} .= ' '.$term->{cnj}.' ';
|
$self->{Sql} .= ' '.$term->{cnj}.' ';
|
||||||
}
|
}
|
||||||
if ( exists($term->{obr}) ) {
|
if ( exists($term->{obr}) ) {
|
||||||
$self->{Sql} .= ' '.str_repeat( '(', $term->{obr} ).' ';
|
$self->{Sql} .= ' '.str_repeat('(', $term->{obr}).' ';
|
||||||
}
|
}
|
||||||
my $value = $term->{val};
|
my $value = $term->{val};
|
||||||
my @value_list;
|
my @value_list;
|
||||||
|
@ -216,17 +221,17 @@ sub Sql {
|
||||||
if ( $temp_value eq 'ZM_SERVER_ID' ) {
|
if ( $temp_value eq 'ZM_SERVER_ID' ) {
|
||||||
$value = "'$ZoneMinder::Config::Config{ZM_SERVER_ID}'";
|
$value = "'$ZoneMinder::Config::Config{ZM_SERVER_ID}'";
|
||||||
# This gets used later, I forget for what
|
# This gets used later, I forget for what
|
||||||
$$self{Server} = new ZoneMinder::Server( $ZoneMinder::Config::Config{ZM_SERVER_ID} );
|
$$self{Server} = new ZoneMinder::Server($ZoneMinder::Config::Config{ZM_SERVER_ID});
|
||||||
} elsif ( $temp_value eq 'NULL' ) {
|
} elsif ( $temp_value eq 'NULL' ) {
|
||||||
$value = $temp_value;
|
$value = $temp_value;
|
||||||
} else {
|
} else {
|
||||||
$value = "'$temp_value'";
|
$value = "'$temp_value'";
|
||||||
# This gets used later, I forget for what
|
# This gets used later, I forget for what
|
||||||
$$self{Server} = new ZoneMinder::Server( $temp_value );
|
$$self{Server} = new ZoneMinder::Server($temp_value);
|
||||||
}
|
}
|
||||||
} elsif ( $term->{attr} eq 'StorageId' ) {
|
} elsif ( $term->{attr} eq 'StorageId' ) {
|
||||||
$value = "'$temp_value'";
|
$value = "'$temp_value'";
|
||||||
$$self{Storage} = new ZoneMinder::Storage( $temp_value );
|
$$self{Storage} = new ZoneMinder::Storage($temp_value);
|
||||||
} elsif ( $term->{attr} eq 'Name'
|
} elsif ( $term->{attr} eq 'Name'
|
||||||
|| $term->{attr} eq 'Cause'
|
|| $term->{attr} eq 'Cause'
|
||||||
|| $term->{attr} eq 'Notes'
|
|| $term->{attr} eq 'Notes'
|
||||||
|
@ -236,10 +241,9 @@ sub Sql {
|
||||||
if ( $temp_value eq 'NULL' ) {
|
if ( $temp_value eq 'NULL' ) {
|
||||||
$value = $temp_value;
|
$value = $temp_value;
|
||||||
} else {
|
} else {
|
||||||
$value = DateTimeToSQL( $temp_value );
|
$value = DateTimeToSQL($temp_value);
|
||||||
if ( !$value ) {
|
if ( !$value ) {
|
||||||
Error( "Error parsing date/time '$temp_value', "
|
Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'");
|
||||||
."skipping filter '$self->{Name}'\n" );
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$value = "'$value'";
|
$value = "'$value'";
|
||||||
|
@ -248,10 +252,9 @@ sub Sql {
|
||||||
if ( $temp_value eq 'NULL' ) {
|
if ( $temp_value eq 'NULL' ) {
|
||||||
$value = $temp_value;
|
$value = $temp_value;
|
||||||
} else {
|
} else {
|
||||||
$value = DateTimeToSQL( $temp_value );
|
$value = DateTimeToSQL($temp_value);
|
||||||
if ( !$value ) {
|
if ( !$value ) {
|
||||||
Error( "Error parsing date/time '$temp_value', "
|
Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'");
|
||||||
."skipping filter '$self->{Name}'\n" );
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$value = "to_days( '$value' )";
|
$value = "to_days( '$value' )";
|
||||||
|
@ -260,10 +263,9 @@ sub Sql {
|
||||||
if ( $temp_value eq 'NULL' ) {
|
if ( $temp_value eq 'NULL' ) {
|
||||||
$value = $temp_value;
|
$value = $temp_value;
|
||||||
} else {
|
} else {
|
||||||
$value = DateTimeToSQL( $temp_value );
|
$value = DateTimeToSQL($temp_value);
|
||||||
if ( !$value ) {
|
if ( !$value ) {
|
||||||
Error( "Error parsing date/time '$temp_value', "
|
Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'");
|
||||||
."skipping filter '$self->{Name}'\n" );
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$value = "extract( hour_second from '$value' )";
|
$value = "extract( hour_second from '$value' )";
|
||||||
|
@ -271,7 +273,7 @@ sub Sql {
|
||||||
} else {
|
} else {
|
||||||
$value = $temp_value;
|
$value = $temp_value;
|
||||||
}
|
}
|
||||||
push( @value_list, $value );
|
push @value_list, $value;
|
||||||
} # end foreach temp_value
|
} # end foreach temp_value
|
||||||
} # end if has an attr
|
} # end if has an attr
|
||||||
if ( $term->{op} ) {
|
if ( $term->{op} ) {
|
||||||
|
@ -290,15 +292,15 @@ sub Sql {
|
||||||
} elsif ( $term->{op} eq 'IS NOT' ) {
|
} elsif ( $term->{op} eq 'IS NOT' ) {
|
||||||
$self->{Sql} .= " IS NOT $value";
|
$self->{Sql} .= " IS NOT $value";
|
||||||
} elsif ( $term->{op} eq '=[]' ) {
|
} elsif ( $term->{op} eq '=[]' ) {
|
||||||
$self->{Sql} .= " in (".join( ",", @value_list ).")";
|
$self->{Sql} .= ' IN ('.join(',', @value_list).')';
|
||||||
} elsif ( $term->{op} eq '!~' ) {
|
} elsif ( $term->{op} eq '!~' ) {
|
||||||
$self->{Sql} .= " not in (".join( ",", @value_list ).")";
|
$self->{Sql} .= ' NOT IN ('.join(',', @value_list).')';
|
||||||
} else {
|
} else {
|
||||||
$self->{Sql} .= ' '.$term->{op}." $value";
|
$self->{Sql} .= ' '.$term->{op}.' '.$value;
|
||||||
}
|
}
|
||||||
} # end if has an operator
|
} # end if has an operator
|
||||||
if ( exists($term->{cbr}) ) {
|
if ( exists($term->{cbr}) ) {
|
||||||
$self->{Sql} .= ' '.str_repeat( ")", $term->{cbr} )." ";
|
$self->{Sql} .= ' '.str_repeat(')', $term->{cbr}).' ';
|
||||||
}
|
}
|
||||||
} # end foreach term
|
} # end foreach term
|
||||||
} # end if terms
|
} # end if terms
|
||||||
|
@ -320,22 +322,22 @@ sub Sql {
|
||||||
# Don't do this, it prevents re-generation and concatenation.
|
# Don't do this, it prevents re-generation and concatenation.
|
||||||
# If the file already exists, then the video won't be re-recreated
|
# If the file already exists, then the video won't be re-recreated
|
||||||
if ( $self->{AutoVideo} ) {
|
if ( $self->{AutoVideo} ) {
|
||||||
push @auto_terms, "E.Videoed = 0";
|
push @auto_terms, 'E.Videoed = 0';
|
||||||
}
|
}
|
||||||
if ( $self->{AutoUpload} ) {
|
if ( $self->{AutoUpload} ) {
|
||||||
push @auto_terms, "E.Uploaded = 0";
|
push @auto_terms, 'E.Uploaded = 0';
|
||||||
}
|
}
|
||||||
if ( $self->{AutoEmail} ) {
|
if ( $self->{AutoEmail} ) {
|
||||||
push @auto_terms, "E.Emailed = 0";
|
push @auto_terms, 'E.Emailed = 0';
|
||||||
}
|
}
|
||||||
if ( $self->{AutoMessage} ) {
|
if ( $self->{AutoMessage} ) {
|
||||||
push @auto_terms, "E.Messaged = 0";
|
push @auto_terms, 'E.Messaged = 0';
|
||||||
}
|
}
|
||||||
if ( $self->{AutoExecute} ) {
|
if ( $self->{AutoExecute} ) {
|
||||||
push @auto_terms, "E.Executed = 0";
|
push @auto_terms, 'E.Executed = 0';
|
||||||
}
|
}
|
||||||
if ( @auto_terms ) {
|
if ( @auto_terms ) {
|
||||||
$sql .= " and ( ".join( ' or ', @auto_terms )." )";
|
$sql .= ' AND ( '.join(' or ', @auto_terms).' )';
|
||||||
}
|
}
|
||||||
if ( !$filter_expr->{sort_field} ) {
|
if ( !$filter_expr->{sort_field} ) {
|
||||||
$filter_expr->{sort_field} = 'StartTime';
|
$filter_expr->{sort_field} = 'StartTime';
|
||||||
|
@ -369,10 +371,10 @@ sub Sql {
|
||||||
} else {
|
} else {
|
||||||
$sort_column = 'E.StartTime';
|
$sort_column = 'E.StartTime';
|
||||||
}
|
}
|
||||||
my $sort_order = $filter_expr->{sort_asc}?'asc':'desc';
|
my $sort_order = $filter_expr->{sort_asc} ? 'ASC' : 'DESC';
|
||||||
$sql .= ' order by '.$sort_column." ".$sort_order;
|
$sql .= ' ORDER BY '.$sort_column." ".$sort_order;
|
||||||
if ( $filter_expr->{limit} ) {
|
if ( $filter_expr->{limit} ) {
|
||||||
$sql .= " limit 0,".$filter_expr->{limit};
|
$sql .= ' LIMIT 0,'.$filter_expr->{limit};
|
||||||
}
|
}
|
||||||
$self->{Sql} = $sql;
|
$self->{Sql} = $sql;
|
||||||
} # end if has Sql
|
} # end if has Sql
|
||||||
|
@ -386,7 +388,7 @@ sub getDiskPercent {
|
||||||
if ( $df =~ /\s(\d+)%/ms ) {
|
if ( $df =~ /\s(\d+)%/ms ) {
|
||||||
$space = $1;
|
$space = $1;
|
||||||
}
|
}
|
||||||
return( $space );
|
return $space;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub getDiskBlocks {
|
sub getDiskBlocks {
|
||||||
|
@ -396,7 +398,7 @@ sub getDiskBlocks {
|
||||||
if ( $df =~ /\s(\d+)\s+\d+\s+\d+%/ms ) {
|
if ( $df =~ /\s(\d+)\s+\d+\s+\d+%/ms ) {
|
||||||
$space = $1;
|
$space = $1;
|
||||||
}
|
}
|
||||||
return( $space );
|
return $space;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub getLoad {
|
sub getLoad {
|
||||||
|
@ -405,9 +407,9 @@ sub getLoad {
|
||||||
my $load = -1;
|
my $load = -1;
|
||||||
if ( $uptime =~ /load average:\s+([\d.]+)/ms ) {
|
if ( $uptime =~ /load average:\s+([\d.]+)/ms ) {
|
||||||
$load = $1;
|
$load = $1;
|
||||||
Info( "Load: $load" );
|
Info("Load: $load");
|
||||||
}
|
}
|
||||||
return( $load );
|
return $load;
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -415,7 +417,7 @@ sub getLoad {
|
||||||
#
|
#
|
||||||
sub strtotime {
|
sub strtotime {
|
||||||
my $dt_str = shift;
|
my $dt_str = shift;
|
||||||
return( Date::Manip::UnixDate( $dt_str, '%s' ) );
|
return Date::Manip::UnixDate($dt_str, '%s');
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -424,18 +426,18 @@ sub strtotime {
|
||||||
sub str_repeat {
|
sub str_repeat {
|
||||||
my $string = shift;
|
my $string = shift;
|
||||||
my $count = shift;
|
my $count = shift;
|
||||||
return( ${string}x${count} );
|
return ${string}x${count};
|
||||||
}
|
}
|
||||||
|
|
||||||
# Formats a date into MySQL format
|
# Formats a date into MySQL format
|
||||||
sub DateTimeToSQL {
|
sub DateTimeToSQL {
|
||||||
my $dt_str = shift;
|
my $dt_str = shift;
|
||||||
my $dt_val = strtotime( $dt_str );
|
my $dt_val = strtotime($dt_str);
|
||||||
if ( !$dt_val ) {
|
if ( !$dt_val ) {
|
||||||
Error( "Unable to parse date string '$dt_str'\n" );
|
Error("Unable to parse date string '$dt_str'");
|
||||||
return( undef );
|
return undef;
|
||||||
}
|
}
|
||||||
return( POSIX::strftime( "%Y-%m-%d %H:%M:%S", localtime( $dt_val ) ) );
|
return POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime($dt_val));
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
|
@ -112,21 +112,23 @@ sub interpret_messages {
|
||||||
my @results;
|
my @results;
|
||||||
foreach my $response ( @responses ) {
|
foreach my $response ( @responses ) {
|
||||||
|
|
||||||
if($verbose) {
|
if ( $verbose ) {
|
||||||
print "Received message:\n" . $response . "\n";
|
print "Received message:\n" . $response . "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
my $result = deserialize_message($svc_discover, $response);
|
my $result = deserialize_message($svc_discover, $response);
|
||||||
if(not $result) {
|
if ( not $result ) {
|
||||||
print "Error deserializing message. No message returned from deserializer.\n" if $verbose;
|
print "Error deserializing message. No message returned from deserializer.\n" if $verbose;
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $xaddr;
|
my $xaddr;
|
||||||
foreach my $l_xaddr (split ' ', $result->get_ProbeMatch()->get_XAddrs()) {
|
my $probe_match = $result->get_ProbeMatch();
|
||||||
|
next if ! $probe_match;
|
||||||
|
foreach my $l_xaddr (split ' ', $probe_match->get_XAddrs()) {
|
||||||
# find IPv4 address
|
# find IPv4 address
|
||||||
print "l_xaddr = $l_xaddr\n" if $verbose;
|
print "l_xaddr = $l_xaddr\n" if $verbose;
|
||||||
if($l_xaddr =~ m|//[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[:/]|) {
|
if ( $l_xaddr =~ m|//[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[:/]| ) {
|
||||||
$xaddr = $l_xaddr;
|
$xaddr = $l_xaddr;
|
||||||
last;
|
last;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -46,56 +46,13 @@ use ZoneMinder::Database qw(:all);
|
||||||
|
|
||||||
use POSIX;
|
use POSIX;
|
||||||
|
|
||||||
use vars qw/ $table $primary_key /;
|
use vars qw/ $table $primary_key %fields/;
|
||||||
$table = 'Storage';
|
$table = 'Storage';
|
||||||
$primary_key = 'Id';
|
$primary_key = 'Id';
|
||||||
#__PACKAGE__->table('Storage');
|
#__PACKAGE__->table('Storage');
|
||||||
#__PACKAGE__->primary_key('Id');
|
#__PACKAGE__->primary_key('Id');
|
||||||
|
%fields = map { $_ => $_ } qw( Id Name Path DoDelete ServerId Type Url DiskSpace Scheme );
|
||||||
|
|
||||||
sub find {
|
|
||||||
shift if $_[0] eq 'ZoneMinder::Storage';
|
|
||||||
my %sql_filters = @_;
|
|
||||||
|
|
||||||
my $sql = 'SELECT * FROM Storage';
|
|
||||||
my @sql_filters;
|
|
||||||
my @sql_values;
|
|
||||||
|
|
||||||
if ( exists $sql_filters{Id} ) {
|
|
||||||
push @sql_filters , ' Id=? ';
|
|
||||||
push @sql_values, $sql_filters{Id};
|
|
||||||
}
|
|
||||||
if ( exists $sql_filters{Name} ) {
|
|
||||||
push @sql_filters , ' Name = ? ';
|
|
||||||
push @sql_values, $sql_filters{Name};
|
|
||||||
}
|
|
||||||
if ( exists $sql_filters{ServerId} ) {
|
|
||||||
push @sql_filters, ' ServerId = ?';
|
|
||||||
push @sql_values, $sql_filters{ServerId};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$sql .= ' WHERE ' . join(' AND ', @sql_filters) if @sql_filters;
|
|
||||||
$sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit};
|
|
||||||
|
|
||||||
my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql )
|
|
||||||
or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() );
|
|
||||||
my $res = $sth->execute( @sql_values )
|
|
||||||
or Fatal( "Can't execute '$sql': ".$sth->errstr() );
|
|
||||||
|
|
||||||
my @results;
|
|
||||||
|
|
||||||
while( my $db_filter = $sth->fetchrow_hashref() ) {
|
|
||||||
my $filter = new ZoneMinder::Storage( $$db_filter{Id}, $db_filter );
|
|
||||||
push @results, $filter;
|
|
||||||
} # end while
|
|
||||||
Debug("SQL: $sql returned " . @results . ' results');
|
|
||||||
return @results;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub find_one {
|
|
||||||
my @results = find(@_);
|
|
||||||
return $results[0] if @results;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub Path {
|
sub Path {
|
||||||
if ( @_ > 1 ) {
|
if ( @_ > 1 ) {
|
||||||
|
|
|
@ -70,7 +70,6 @@ if ( !$id ) {
|
||||||
|
|
||||||
( $id ) = $id =~ /^(\w+)$/;
|
( $id ) = $id =~ /^(\w+)$/;
|
||||||
|
|
||||||
|
|
||||||
my $sock_file = $Config{ZM_PATH_SOCKS}.'/zmcontrol-'.$id.'.sock';
|
my $sock_file = $Config{ZM_PATH_SOCKS}.'/zmcontrol-'.$id.'.sock';
|
||||||
Debug("zmcontrol: arg string: $arg_string sock file $sock_file");
|
Debug("zmcontrol: arg string: $arg_string sock file $sock_file");
|
||||||
|
|
||||||
|
|
|
@ -240,6 +240,7 @@ sub getFilters {
|
||||||
or AutoDelete = 1
|
or AutoDelete = 1
|
||||||
or UpdateDiskSpace = 1
|
or UpdateDiskSpace = 1
|
||||||
or AutoMove = 1
|
or AutoMove = 1
|
||||||
|
or AutoCopy = 1
|
||||||
) ORDER BY Name';
|
) ORDER BY Name';
|
||||||
my $sth = $dbh->prepare_cached($sql)
|
my $sth = $dbh->prepare_cached($sql)
|
||||||
or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
|
or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
|
||||||
|
@ -271,7 +272,7 @@ sub checkFilter {
|
||||||
my $filter = shift;
|
my $filter = shift;
|
||||||
|
|
||||||
my @Events = $filter->Execute();
|
my @Events = $filter->Execute();
|
||||||
Info(
|
Debug(
|
||||||
join(' ',
|
join(' ',
|
||||||
'Checking filter', $filter->{Name},
|
'Checking filter', $filter->{Name},
|
||||||
join(', ',
|
join(', ',
|
||||||
|
@ -283,6 +284,7 @@ sub checkFilter {
|
||||||
($filter->{AutoMessage}?'message':()),
|
($filter->{AutoMessage}?'message':()),
|
||||||
($filter->{AutoExecute}?'execute':()),
|
($filter->{AutoExecute}?'execute':()),
|
||||||
($filter->{AutoMove}?'move':()),
|
($filter->{AutoMove}?'move':()),
|
||||||
|
($filter->{AutoCopy}?'copy':()),
|
||||||
($filter->{UpdateDiskSpace}?'update disk space':()),
|
($filter->{UpdateDiskSpace}?'update disk space':()),
|
||||||
),
|
),
|
||||||
'returned' , scalar @Events , 'events',
|
'returned' , scalar @Events , 'events',
|
||||||
|
@ -300,9 +302,9 @@ sub checkFilter {
|
||||||
Info("Archiving event $Event->{Id}");
|
Info("Archiving event $Event->{Id}");
|
||||||
# Do it individually to avoid locking up the table for new events
|
# Do it individually to avoid locking up the table for new events
|
||||||
my $sql = 'UPDATE Events SET Archived = 1 WHERE Id = ?';
|
my $sql = 'UPDATE Events SET Archived = 1 WHERE Id = ?';
|
||||||
my $sth = $dbh->prepare_cached( $sql )
|
my $sth = $dbh->prepare_cached($sql)
|
||||||
or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
|
or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
|
||||||
my $res = $sth->execute( $Event->{Id} )
|
my $res = $sth->execute($Event->{Id})
|
||||||
or Error("Unable to execute '$sql': ".$dbh->errstr());
|
or Error("Unable to execute '$sql': ".$dbh->errstr());
|
||||||
}
|
}
|
||||||
if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) {
|
if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) {
|
||||||
|
@ -343,6 +345,23 @@ sub checkFilter {
|
||||||
$_ = $Event->MoveTo($NewStorage);
|
$_ = $Event->MoveTo($NewStorage);
|
||||||
Error($_) if $_;
|
Error($_) if $_;
|
||||||
}
|
}
|
||||||
|
if ( $filter->{AutoCopy} ) {
|
||||||
|
# Copy To is different from MoveTo in that it JUST copies the files
|
||||||
|
# So we still need to update the Event object with the new SecondaryStorageId
|
||||||
|
my $NewStorage = ZoneMinder::Storage->find_one(Id=>$filter->{AutoCopyTo});
|
||||||
|
if ( $NewStorage ) {
|
||||||
|
$_ = $Event->CopyTo($NewStorage);
|
||||||
|
if ( $_ ) {
|
||||||
|
$ZoneMinder::Database::dbh->commit();
|
||||||
|
Error($_);
|
||||||
|
} else {
|
||||||
|
$Event->save({SecondaryStorageId=>$$NewStorage{Id}});
|
||||||
|
$ZoneMinder::Database::dbh->commit();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Error("No storage area found for copy to operation. AutoCopyTo was $$filter{AutoCopyTo}");
|
||||||
|
}
|
||||||
|
} # end if AutoCopy
|
||||||
|
|
||||||
if ( $filter->{UpdateDiskSpace} ) {
|
if ( $filter->{UpdateDiskSpace} ) {
|
||||||
$ZoneMinder::Database::dbh->begin_work();
|
$ZoneMinder::Database::dbh->begin_work();
|
||||||
|
@ -361,7 +380,7 @@ sub checkFilter {
|
||||||
$ZoneMinder::Database::dbh->commit();
|
$ZoneMinder::Database::dbh->commit();
|
||||||
} # end if UpdateDiskSpace
|
} # end if UpdateDiskSpace
|
||||||
} # end foreach event
|
} # end foreach event
|
||||||
}
|
} # end sub checkFilter
|
||||||
|
|
||||||
sub generateVideo {
|
sub generateVideo {
|
||||||
my $filter = shift;
|
my $filter = shift;
|
||||||
|
@ -448,10 +467,10 @@ sub generateImage {
|
||||||
} elsif ( -r $capture_image_path ) {
|
} elsif ( -r $capture_image_path ) {
|
||||||
$image_path = $capture_image_path;
|
$image_path = $capture_image_path;
|
||||||
} elsif ( -r $video_path ) {
|
} elsif ( -r $video_path ) {
|
||||||
my $command ="ffmpeg -ss $$frame{Delta} -i '$video_path' -frames:v 1 '$capture_image_path'";
|
my $command ="ffmpeg -nostdin -ss $$frame{Delta} -i '$video_path' -frames:v 1 '$capture_image_path'";
|
||||||
#$command = "ffmpeg -y -v 0 -i $video_path -vf 'select=gte(n\\,$$frame{FrameId}),setpts=PTS-STARTPTS' -vframes 1 -f image2 $capture_image_path";
|
#$command = "ffmpeg -y -v 0 -i $video_path -vf 'select=gte(n\\,$$frame{FrameId}),setpts=PTS-STARTPTS' -vframes 1 -f image2 $capture_image_path";
|
||||||
my $output = qx($command);
|
my $output = qx($command);
|
||||||
chomp( $output );
|
chomp($output);
|
||||||
my $status = $? >> 8;
|
my $status = $? >> 8;
|
||||||
if ( $status || logDebugging() ) {
|
if ( $status || logDebugging() ) {
|
||||||
Debug("Output: $output");
|
Debug("Output: $output");
|
||||||
|
@ -623,7 +642,7 @@ sub substituteTags {
|
||||||
my $Monitor = $Event->Monitor() if $need_monitor;
|
my $Monitor = $Event->Monitor() if $need_monitor;
|
||||||
|
|
||||||
# Do we need the image information too?
|
# Do we need the image information too?
|
||||||
my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM|EI1A|EIMA)%/;
|
my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM|EI1A|EIMA|EIMOD)%/;
|
||||||
my $first_alarm_frame;
|
my $first_alarm_frame;
|
||||||
my $max_alarm_frame;
|
my $max_alarm_frame;
|
||||||
my $max_alarm_score = 0;
|
my $max_alarm_score = 0;
|
||||||
|
@ -685,6 +704,8 @@ sub substituteTags {
|
||||||
my $path = generateImage($Event, $first_alarm_frame);
|
my $path = generateImage($Event, $first_alarm_frame);
|
||||||
if ( -e $path ) {
|
if ( -e $path ) {
|
||||||
push @$attachments_ref, { type=>'image/jpeg', path=>$path };
|
push @$attachments_ref, { type=>'image/jpeg', path=>$path };
|
||||||
|
} else {
|
||||||
|
Warning("Path to first image does not exist at $path");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -697,18 +718,20 @@ sub substituteTags {
|
||||||
if ( -e $path ) {
|
if ( -e $path ) {
|
||||||
push @$attachments_ref, { type=>'image/jpeg', path=>$path };
|
push @$attachments_ref, { type=>'image/jpeg', path=>$path };
|
||||||
} else {
|
} else {
|
||||||
Warning("No image for EIM");
|
Warning("No image for EIM at $path");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $text =~ s/%EI1A%//g ) {
|
if ( $text =~ s/%EI1A%//g ) {
|
||||||
my $path = generateImage($Event, $first_alarm_frame, 'analyse');
|
my $path = generateImage($Event, $first_alarm_frame, 'analyse');
|
||||||
if ( -e $path ) {
|
if ( -e $path ) {
|
||||||
push @$attachments_ref, { type=>'image/jpeg', path=>$path };
|
push @$attachments_ref, { type=>'image/jpeg', path=>$path };
|
||||||
} else {
|
} else {
|
||||||
Warning("No image for EI1A");
|
Warning("No image for EI1A at $path");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $text =~ s/%EIMA%//g ) {
|
if ( $text =~ s/%EIMA%//g ) {
|
||||||
# Don't attach the same image twice
|
# Don't attach the same image twice
|
||||||
if ( !@$attachments_ref
|
if ( !@$attachments_ref
|
||||||
|
@ -718,11 +741,13 @@ sub substituteTags {
|
||||||
if ( -e $path ) {
|
if ( -e $path ) {
|
||||||
push @$attachments_ref, { type=>'image/jpeg', path=>$path };
|
push @$attachments_ref, { type=>'image/jpeg', path=>$path };
|
||||||
} else {
|
} else {
|
||||||
Warning('No image for EIMA');
|
Warning("No image for EIMA at $path");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $text =~ s/%EIMOD%//g ) {
|
if ( $text =~ s/%EIMOD%//g ) {
|
||||||
|
$text =~ s/%EIMOD%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=objdetect/g;
|
||||||
my $path = $Event->Path().'/objdetect.jpg';
|
my $path = $Event->Path().'/objdetect.jpg';
|
||||||
if ( -e $path ) {
|
if ( -e $path ) {
|
||||||
push @$attachments_ref, { type=>'image/jpeg', path=>$path };
|
push @$attachments_ref, { type=>'image/jpeg', path=>$path };
|
||||||
|
|
|
@ -88,13 +88,13 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
|
||||||
logInit();
|
logInit();
|
||||||
logSetSignal();
|
logSetSignal();
|
||||||
|
|
||||||
Info( "Trigger daemon starting" );
|
Info('Trigger daemon starting');
|
||||||
|
|
||||||
my $dbh = zmDbConnect();
|
my $dbh = zmDbConnect();
|
||||||
|
|
||||||
my $base_rin = '';
|
my $base_rin = '';
|
||||||
foreach my $connection ( @connections ) {
|
foreach my $connection ( @connections ) {
|
||||||
Info( "Opening connection '$connection->{name}'" );
|
Info("Opening connection '$connection->{name}'");
|
||||||
$connection->open();
|
$connection->open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,32 +118,32 @@ my $win = $rin;
|
||||||
my $ein = $win;
|
my $ein = $win;
|
||||||
my $timeout = SELECT_TIMEOUT;
|
my $timeout = SELECT_TIMEOUT;
|
||||||
my %actions;
|
my %actions;
|
||||||
while( 1 ) {
|
while (1) {
|
||||||
$rin = $base_rin;
|
$rin = $base_rin;
|
||||||
# Add the file descriptors of any spawned connections
|
# Add the file descriptors of any spawned connections
|
||||||
foreach my $fileno ( keys(%spawned_connections) ) {
|
foreach my $fileno ( keys %spawned_connections ) {
|
||||||
vec( $rin, $fileno, 1 ) = 1;
|
vec($rin, $fileno, 1) = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $nfound = select( my $rout = $rin, undef, my $eout = $ein, $timeout );
|
my $nfound = select(my $rout = $rin, undef, my $eout = $ein, $timeout);
|
||||||
if ( $nfound > 0 ) {
|
if ( $nfound > 0 ) {
|
||||||
Debug( "Got input from $nfound connections" );
|
Debug("Got input from $nfound connections");
|
||||||
foreach my $connection ( @in_select_connections ) {
|
foreach my $connection ( @in_select_connections ) {
|
||||||
if ( vec( $rout, $connection->fileno(), 1 ) ) {
|
if ( vec($rout, $connection->fileno(), 1) ) {
|
||||||
Debug( 'Got input from connection '
|
Debug('Got input from connection '
|
||||||
.$connection->name()
|
.$connection->name()
|
||||||
.' ('
|
.' ('
|
||||||
.$connection->fileno()
|
.$connection->fileno()
|
||||||
.")"
|
.')'
|
||||||
);
|
);
|
||||||
if ( $connection->spawns() ) {
|
if ( $connection->spawns() ) {
|
||||||
my $new_connection = $connection->accept();
|
my $new_connection = $connection->accept();
|
||||||
$spawned_connections{$new_connection->fileno()} = $new_connection;
|
$spawned_connections{$new_connection->fileno()} = $new_connection;
|
||||||
Debug( 'Added new spawned connection ('
|
Debug('Added new spawned connection ('
|
||||||
.$new_connection->fileno()
|
.$new_connection->fileno()
|
||||||
.'), '
|
.'), '
|
||||||
.int(keys(%spawned_connections))
|
.int(keys(%spawned_connections))
|
||||||
." spawned connections"
|
.' spawned connections'
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
my $messages = $connection->getMessages();
|
my $messages = $connection->getMessages();
|
||||||
|
@ -152,30 +152,30 @@ while( 1 ) {
|
||||||
handleMessage( $connection, $message );
|
handleMessage( $connection, $message );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} # end if connection->spawns
|
||||||
}
|
} # end if vec
|
||||||
} # end foreach connection
|
} # end foreach connection
|
||||||
|
|
||||||
foreach my $connection ( values(%spawned_connections) ) {
|
foreach my $connection ( values(%spawned_connections) ) {
|
||||||
if ( vec( $rout, $connection->fileno(), 1 ) ) {
|
if ( vec($rout, $connection->fileno(), 1) ) {
|
||||||
Debug( 'Got input from spawned connection '
|
Debug('Got input from spawned connection '
|
||||||
.$connection->name()
|
.$connection->name()
|
||||||
.' ('
|
.' ('
|
||||||
.$connection->fileno()
|
.$connection->fileno()
|
||||||
.")"
|
.')'
|
||||||
);
|
);
|
||||||
my $messages = $connection->getMessages();
|
my $messages = $connection->getMessages();
|
||||||
if ( defined($messages) ) {
|
if ( defined($messages) ) {
|
||||||
foreach my $message ( @$messages ) {
|
foreach my $message ( @$messages ) {
|
||||||
handleMessage( $connection, $message );
|
handleMessage($connection, $message);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
delete( $spawned_connections{$connection->fileno()} );
|
delete $spawned_connections{$connection->fileno()};
|
||||||
Debug( 'Removed spawned connection ('
|
Debug('Removed spawned connection ('
|
||||||
.$connection->fileno()
|
.$connection->fileno()
|
||||||
.'), '
|
.'), '
|
||||||
.int(keys(%spawned_connections))
|
.int(keys(%spawned_connections))
|
||||||
." spawned connections"
|
.' spawned connections'
|
||||||
);
|
);
|
||||||
$connection->close();
|
$connection->close();
|
||||||
}
|
}
|
||||||
|
@ -185,7 +185,7 @@ while( 1 ) {
|
||||||
if ( $! == EINTR ) {
|
if ( $! == EINTR ) {
|
||||||
# Do nothing
|
# Do nothing
|
||||||
} else {
|
} else {
|
||||||
Fatal( "Can't select: $!" );
|
Fatal("Can't select: $!");
|
||||||
}
|
}
|
||||||
} # end if select returned activitiy
|
} # end if select returned activitiy
|
||||||
|
|
||||||
|
@ -194,14 +194,14 @@ while( 1 ) {
|
||||||
my $messages = $connection->getMessages();
|
my $messages = $connection->getMessages();
|
||||||
if ( defined($messages) ) {
|
if ( defined($messages) ) {
|
||||||
foreach my $message ( @$messages ) {
|
foreach my $message ( @$messages ) {
|
||||||
handleMessage( $connection, $message );
|
handleMessage($connection, $message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check for alarms that might have happened
|
# Check for alarms that might have happened
|
||||||
my @out_messages;
|
my @out_messages;
|
||||||
foreach my $monitor ( values(%monitors) ) {
|
foreach my $monitor ( values %monitors ) {
|
||||||
|
|
||||||
if ( ! zmMemVerify($monitor) ) {
|
if ( ! zmMemVerify($monitor) ) {
|
||||||
# Our attempt to verify the memory handle failed. We should reload the monitors.
|
# Our attempt to verify the memory handle failed. We should reload the monitors.
|
||||||
|
@ -225,7 +225,7 @@ while( 1 ) {
|
||||||
|| ($last_event != $monitor->{LastEvent})
|
|| ($last_event != $monitor->{LastEvent})
|
||||||
) {
|
) {
|
||||||
# A new event
|
# A new event
|
||||||
push( @out_messages, $monitor->{Id}."|on|".time()."|".$last_event );
|
push @out_messages, $monitor->{Id}.'|on|'.time().'|'.$last_event;
|
||||||
} else {
|
} else {
|
||||||
# The same one as last time, so ignore it
|
# The same one as last time, so ignore it
|
||||||
# Do nothing
|
# Do nothing
|
||||||
|
@ -236,42 +236,43 @@ while( 1 ) {
|
||||||
($state == STATE_TAPE && $monitor->{LastState} != STATE_TAPE)
|
($state == STATE_TAPE && $monitor->{LastState} != STATE_TAPE)
|
||||||
) {
|
) {
|
||||||
# Out of alarm state
|
# Out of alarm state
|
||||||
push( @out_messages, $monitor->{Id}.'|off|'.time().'|'.$last_event );
|
push @out_messages, $monitor->{Id}.'|off|'.time().'|'.$last_event;
|
||||||
} elsif (
|
} elsif (
|
||||||
defined($monitor->{LastEvent})
|
defined($monitor->{LastEvent})
|
||||||
&&
|
&&
|
||||||
($last_event != $monitor->{LastEvent})
|
($last_event != $monitor->{LastEvent})
|
||||||
) {
|
) {
|
||||||
# We've missed a whole event
|
# We've missed a whole event
|
||||||
push( @out_messages, $monitor->{Id}.'|on|'.time().'|'.$last_event );
|
push @out_messages, $monitor->{Id}.'|on|'.time().'|'.$last_event;
|
||||||
push( @out_messages, $monitor->{Id}.'|off|'.time().'|'.$last_event );
|
push @out_messages, $monitor->{Id}.'|off|'.time().'|'.$last_event;
|
||||||
}
|
}
|
||||||
$monitor->{LastState} = $state;
|
$monitor->{LastState} = $state;
|
||||||
$monitor->{LastEvent} = $last_event;
|
$monitor->{LastEvent} = $last_event;
|
||||||
} # end foreach monitor
|
} # end foreach monitor
|
||||||
|
|
||||||
foreach my $connection ( @out_connections ) {
|
foreach my $connection ( @out_connections ) {
|
||||||
if ( $connection->canWrite() ) {
|
if ( $connection->canWrite() ) {
|
||||||
$connection->putMessages( \@out_messages );
|
$connection->putMessages(\@out_messages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach my $connection ( values(%spawned_connections) ) {
|
|
||||||
|
foreach my $connection ( values %spawned_connections ) {
|
||||||
if ( $connection->canWrite() ) {
|
if ( $connection->canWrite() ) {
|
||||||
$connection->putMessages( \@out_messages );
|
$connection->putMessages(\@out_messages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( my @action_times = keys(%actions) ) {
|
if ( my @action_times = keys(%actions) ) {
|
||||||
Debug( "Checking for timed actions" );
|
Debug('Checking for timed actions');
|
||||||
my $now = time();
|
my $now = time();
|
||||||
foreach my $action_time ( sort( grep { $_ < $now } @action_times ) ) {
|
foreach my $action_time ( sort( grep { $_ < $now } @action_times ) ) {
|
||||||
Info( "Found actions expiring at $action_time" );
|
Info("Found " . scalar @{$actions{$action_time}} . "actions expiring at $action_time");
|
||||||
foreach my $action ( @{$actions{$action_time}} ) {
|
foreach my $action ( @{$actions{$action_time}} ) {
|
||||||
my $connection = $action->{connection};
|
my $connection = $action->{connection};
|
||||||
my $message = $action->{message};
|
Info("Found action '$$action{message}'");
|
||||||
Info( "Found action '$message'" );
|
handleMessage($connection, $$action{message});
|
||||||
handleMessage( $connection, $message );
|
|
||||||
}
|
}
|
||||||
delete( $actions{$action_time} );
|
delete $actions{$action_time};
|
||||||
}
|
}
|
||||||
} # end if have timed actions
|
} # end if have timed actions
|
||||||
|
|
||||||
|
@ -280,15 +281,16 @@ while( 1 ) {
|
||||||
my $messages = $connection->timedActions();
|
my $messages = $connection->timedActions();
|
||||||
if ( defined($messages) ) {
|
if ( defined($messages) ) {
|
||||||
foreach my $message ( @$messages ) {
|
foreach my $message ( @$messages ) {
|
||||||
handleMessage( $connection, $message );
|
handleMessage($connection, $message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach my $connection ( values(%spawned_connections) ) {
|
|
||||||
|
foreach my $connection ( values %spawned_connections ) {
|
||||||
my $messages = $connection->timedActions();
|
my $messages = $connection->timedActions();
|
||||||
if ( defined($messages) ) {
|
if ( defined($messages) ) {
|
||||||
foreach my $message ( @$messages ) {
|
foreach my $message ( @$messages ) {
|
||||||
handleMessage( $connection, $message );
|
handleMessage($connection, $message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -317,14 +319,14 @@ exit;
|
||||||
sub loadMonitor {
|
sub loadMonitor {
|
||||||
my $monitor = shift;
|
my $monitor = shift;
|
||||||
|
|
||||||
Debug( "Loading monitor $monitor" );
|
Debug("Loading monitor $monitor");
|
||||||
zmMemInvalidate( $monitor );
|
zmMemInvalidate($monitor);
|
||||||
|
|
||||||
if ( zmMemVerify( $monitor ) ) { # This will re-init shared memory
|
if ( zmMemVerify($monitor) ) { # This will re-init shared memory
|
||||||
$monitor->{LastState} = zmGetMonitorState( $monitor );
|
$monitor->{LastState} = zmGetMonitorState($monitor);
|
||||||
$monitor->{LastEvent} = zmGetLastEvent( $monitor );
|
$monitor->{LastEvent} = zmGetLastEvent($monitor);
|
||||||
}
|
}
|
||||||
}
|
} # end sub loadMonitor
|
||||||
|
|
||||||
sub loadMonitors {
|
sub loadMonitors {
|
||||||
Debug('Loading monitors');
|
Debug('Loading monitors');
|
||||||
|
@ -332,18 +334,19 @@ sub loadMonitors {
|
||||||
|
|
||||||
my %new_monitors = ();
|
my %new_monitors = ();
|
||||||
|
|
||||||
my $sql = "SELECT * FROM Monitors
|
my $sql = q`SELECT * FROM Monitors
|
||||||
WHERE find_in_set( Function, 'Modect,Mocord,Nodect' )".
|
WHERE find_in_set( Function, 'Modect,Mocord,Nodect' )`.
|
||||||
( $Config{ZM_SERVER_ID} ? 'AND ServerId=?' : '' )
|
( $Config{ZM_SERVER_ID} ? ' AND ServerId=?' : '' )
|
||||||
;
|
;
|
||||||
my $sth = $dbh->prepare_cached( $sql )
|
my $sth = $dbh->prepare_cached( $sql )
|
||||||
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||||
my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () )
|
my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () )
|
||||||
or Fatal( "Can't execute: ".$sth->errstr() );
|
or Fatal( "Can't execute: ".$sth->errstr() );
|
||||||
while( my $monitor = $sth->fetchrow_hashref() ) {
|
|
||||||
if ( zmMemVerify( $monitor ) ) { # This will re-init shared memory
|
while ( my $monitor = $sth->fetchrow_hashref() ) {
|
||||||
$monitor->{LastState} = zmGetMonitorState( $monitor );
|
if ( zmMemVerify($monitor) ) { # This will re-init shared memory
|
||||||
$monitor->{LastEvent} = zmGetLastEvent( $monitor );
|
$monitor->{LastState} = zmGetMonitorState($monitor);
|
||||||
|
$monitor->{LastEvent} = zmGetLastEvent($monitor);
|
||||||
}
|
}
|
||||||
$new_monitors{$monitor->{Id}} = $monitor;
|
$new_monitors{$monitor->{Id}} = $monitor;
|
||||||
} # end while fetchrow
|
} # end while fetchrow
|
||||||
|
@ -367,7 +370,7 @@ sub handleMessage {
|
||||||
}
|
}
|
||||||
Debug("Found monitor for id '$id'");
|
Debug("Found monitor for id '$id'");
|
||||||
|
|
||||||
next if ( !zmMemVerify($monitor) );
|
next if !zmMemVerify($monitor);
|
||||||
|
|
||||||
Debug("Handling action '$action'");
|
Debug("Handling action '$action'");
|
||||||
if ( $action =~ /^(enable|disable)(?:\+(\d+))?$/ ) {
|
if ( $action =~ /^(enable|disable)(?:\+(\d+))?$/ ) {
|
||||||
|
@ -412,20 +415,20 @@ sub handleMessage {
|
||||||
zmTriggerShowtext($monitor, $showtext) if defined($showtext);
|
zmTriggerShowtext($monitor, $showtext) if defined($showtext);
|
||||||
Info("Trigger '$trigger'");
|
Info("Trigger '$trigger'");
|
||||||
# Wait til it's finished
|
# Wait til it's finished
|
||||||
while( zmInAlarm($monitor)
|
while ( zmInAlarm($monitor)
|
||||||
&& ($last_event == zmGetLastEvent($monitor))
|
&& ($last_event == zmGetLastEvent($monitor))
|
||||||
) {
|
) {
|
||||||
# Tenth of a second
|
# Tenth of a second
|
||||||
usleep(100000);
|
usleep(100000);
|
||||||
}
|
}
|
||||||
zmTriggerEventCancel($monitor);
|
zmTriggerEventCancel($monitor);
|
||||||
}
|
} # end if delay or not
|
||||||
} # end if trigger is on or off
|
} # end if trigger is on or off
|
||||||
} elsif( $action eq 'cancel' ) {
|
} elsif ( $action eq 'cancel' ) {
|
||||||
zmTriggerEventCancel($monitor);
|
zmTriggerEventCancel($monitor);
|
||||||
zmTriggerShowtext($monitor, $showtext) if defined($showtext);
|
zmTriggerShowtext($monitor, $showtext) if defined($showtext);
|
||||||
Info('Cancelled event');
|
Info('Cancelled event');
|
||||||
} elsif( $action eq 'show' ) {
|
} elsif ( $action eq 'show' ) {
|
||||||
zmTriggerShowtext( $monitor, $showtext );
|
zmTriggerShowtext( $monitor, $showtext );
|
||||||
Info("Updated show text to '$showtext'");
|
Info("Updated show text to '$showtext'");
|
||||||
} else {
|
} else {
|
||||||
|
@ -439,11 +442,26 @@ sub handleDelay {
|
||||||
my $action_text = shift;
|
my $action_text = shift;
|
||||||
|
|
||||||
my $action_time = time()+$delay;
|
my $action_time = time()+$delay;
|
||||||
|
|
||||||
|
# Need to check and cancel previous actions. See issue #2619
|
||||||
|
foreach my $a_time ( keys %actions ) {
|
||||||
|
if ( $a_time <= $action_time ) {
|
||||||
|
for ( my $i = 0; $i < @{$actions{$a_time}}; $i ++ ) {
|
||||||
|
my $action = $actions{$a_time}[$i];
|
||||||
|
if ( $$action{message} eq $action_text ) {
|
||||||
|
Info("Found duplicate action '$$action{message}' at $a_time, cancelling it");
|
||||||
|
splice @{$actions{$a_time}}, $i, 1;
|
||||||
|
}
|
||||||
|
} # end foreach action
|
||||||
|
delete $actions{$a_time} if !@{$actions{$a_time}};
|
||||||
|
} # end if
|
||||||
|
} # end foreach action_time
|
||||||
|
|
||||||
my $action_array = $actions{$action_time};
|
my $action_array = $actions{$action_time};
|
||||||
if ( !$action_array ) {
|
if ( !$action_array ) {
|
||||||
$action_array = $actions{$action_time} = [];
|
$action_array = $actions{$action_time} = [];
|
||||||
}
|
}
|
||||||
push( @$action_array, { connection=>$connection, message=>$action_text } );
|
push @$action_array, { connection=>$connection, message=>$action_text };
|
||||||
Debug("Added timed event '$action_text', expires at $action_time (+$delay secs)");
|
Debug("Added timed event '$action_text', expires at $action_time (+$delay secs)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,8 @@ configuring upgrades etc, including on the fly upgrades.
|
||||||
use strict;
|
use strict;
|
||||||
use bytes;
|
use bytes;
|
||||||
use version;
|
use version;
|
||||||
|
use Crypt::Eksblowfish::Bcrypt;
|
||||||
|
use Data::Entropy::Algorithms qw(rand_bits);
|
||||||
|
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
#
|
#
|
||||||
|
@ -312,6 +314,7 @@ if ( $migrateEvents ) {
|
||||||
if ( $freshen ) {
|
if ( $freshen ) {
|
||||||
print( "\nFreshening configuration in database\n" );
|
print( "\nFreshening configuration in database\n" );
|
||||||
migratePaths();
|
migratePaths();
|
||||||
|
migratePasswords();
|
||||||
ZoneMinder::Config::loadConfigFromDB();
|
ZoneMinder::Config::loadConfigFromDB();
|
||||||
ZoneMinder::Config::saveConfigToDB();
|
ZoneMinder::Config::saveConfigToDB();
|
||||||
}
|
}
|
||||||
|
@ -999,6 +1002,26 @@ sub patchDB {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub migratePasswords {
|
||||||
|
print ("Migratings passwords, if any...\n");
|
||||||
|
my $sql = "select * from Users";
|
||||||
|
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||||
|
my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() );
|
||||||
|
while( my $user = $sth->fetchrow_hashref() ) {
|
||||||
|
my $scheme = substr($user->{Password}, 0, 1);
|
||||||
|
if ($scheme eq "*") {
|
||||||
|
print ("-->".$user->{Username}. " password will be migrated\n");
|
||||||
|
my $salt = Crypt::Eksblowfish::Bcrypt::en_base64(rand_bits(16*8));
|
||||||
|
my $settings = '$2a$10$'.$salt;
|
||||||
|
my $pass_hash = Crypt::Eksblowfish::Bcrypt::bcrypt($user->{Password},$settings);
|
||||||
|
my $new_pass_hash = "-ZM-".$pass_hash;
|
||||||
|
$sql = "UPDATE Users SET PASSWORD=? WHERE Username=?";
|
||||||
|
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||||
|
my $res = $sth->execute($new_pass_hash, $user->{Username}) or die( "Can't execute: ".$sth->errstr() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sub migratePaths {
|
sub migratePaths {
|
||||||
|
|
||||||
my $customConfigFile = '@ZM_CONFIG_SUBDIR@/zmcustom.conf';
|
my $customConfigFile = '@ZM_CONFIG_SUBDIR@/zmcustom.conf';
|
||||||
|
|
|
@ -104,7 +104,7 @@ while( 1 ) {
|
||||||
if ( !$capture_time ) {
|
if ( !$capture_time ) {
|
||||||
my $startup_time = zmGetStartupTime($monitor);
|
my $startup_time = zmGetStartupTime($monitor);
|
||||||
if ( ( $now - $startup_time ) > $Config{ZM_WATCH_MAX_DELAY} ) {
|
if ( ( $now - $startup_time ) > $Config{ZM_WATCH_MAX_DELAY} ) {
|
||||||
Info(
|
Warning(
|
||||||
"Restarting capture daemon for $$monitor{Name}, no image since startup. ".
|
"Restarting capture daemon for $$monitor{Name}, no image since startup. ".
|
||||||
"Startup time was $startup_time - now $now > $Config{ZM_WATCH_MAX_DELAY}"
|
"Startup time was $startup_time - now $now > $Config{ZM_WATCH_MAX_DELAY}"
|
||||||
);
|
);
|
||||||
|
@ -126,7 +126,7 @@ while( 1 ) {
|
||||||
my $image_delay = $now - $capture_time;
|
my $image_delay = $now - $capture_time;
|
||||||
Debug("Monitor $monitor->{Id} last captured $image_delay seconds ago, max is $max_image_delay");
|
Debug("Monitor $monitor->{Id} last captured $image_delay seconds ago, max is $max_image_delay");
|
||||||
if ( $image_delay > $max_image_delay ) {
|
if ( $image_delay > $max_image_delay ) {
|
||||||
Info("Restarting capture daemon for "
|
Warning("Restarting capture daemon for "
|
||||||
.$monitor->{Name}.", time since last capture $image_delay seconds ($now-$capture_time)"
|
.$monitor->{Name}.", time since last capture $image_delay seconds ($now-$capture_time)"
|
||||||
);
|
);
|
||||||
$restart = 1;
|
$restart = 1;
|
||||||
|
@ -169,7 +169,7 @@ while( 1 ) {
|
||||||
my $image_delay = $now-$image_time;
|
my $image_delay = $now-$image_time;
|
||||||
Debug("Monitor $monitor->{Id} last analysed $image_delay seconds ago, max is $max_image_delay");
|
Debug("Monitor $monitor->{Id} last analysed $image_delay seconds ago, max is $max_image_delay");
|
||||||
if ( $image_delay > $max_image_delay ) {
|
if ( $image_delay > $max_image_delay ) {
|
||||||
Info("Analysis daemon for $$monitor{Id} $$monitor{Name} needs restarting,"
|
Warning("Analysis daemon for $$monitor{Id} $$monitor{Name} needs restarting,"
|
||||||
." time since last analysis $image_delay seconds ($now-$image_time)"
|
." time since last analysis $image_delay seconds ($now-$image_time)"
|
||||||
);
|
);
|
||||||
$restart = 1;
|
$restart = 1;
|
||||||
|
|
|
@ -13,12 +13,14 @@ set(ZM_BIN_SRC_FILES
|
||||||
zm_config.cpp
|
zm_config.cpp
|
||||||
zm_coord.cpp
|
zm_coord.cpp
|
||||||
zm_curl_camera.cpp
|
zm_curl_camera.cpp
|
||||||
|
zm_crypt.cpp
|
||||||
zm.cpp
|
zm.cpp
|
||||||
zm_db.cpp
|
zm_db.cpp
|
||||||
zm_logger.cpp
|
zm_logger.cpp
|
||||||
zm_event.cpp
|
zm_event.cpp
|
||||||
zm_eventstream.cpp
|
zm_eventstream.cpp
|
||||||
zm_exception.cpp
|
zm_exception.cpp
|
||||||
|
zm_fifo.cpp
|
||||||
zm_file_camera.cpp zm_ffmpeg_camera.cpp
|
zm_file_camera.cpp zm_ffmpeg_camera.cpp
|
||||||
zm_frame.cpp
|
zm_frame.cpp
|
||||||
zm_group.cpp
|
zm_group.cpp
|
||||||
|
@ -61,14 +63,19 @@ set(ZM_BIN_SRC_FILES
|
||||||
|
|
||||||
# A fix for cmake recompiling the source files for every target.
|
# A fix for cmake recompiling the source files for every target.
|
||||||
add_library(zm STATIC ${ZM_BIN_SRC_FILES})
|
add_library(zm STATIC ${ZM_BIN_SRC_FILES})
|
||||||
|
link_directories(libbcrypt)
|
||||||
|
|
||||||
add_executable(zmc zmc.cpp)
|
add_executable(zmc zmc.cpp)
|
||||||
add_executable(zmu zmu.cpp)
|
add_executable(zmu zmu.cpp)
|
||||||
add_executable(zms zms.cpp)
|
add_executable(zms zms.cpp)
|
||||||
|
|
||||||
target_link_libraries(zmc zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS})
|
# JWT is a header only library.
|
||||||
target_link_libraries(zmu zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS})
|
include_directories(libbcrypt/include/bcrypt)
|
||||||
target_link_libraries(zms zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS})
|
include_directories(jwt-cpp/include/jwt-cpp)
|
||||||
|
|
||||||
|
target_link_libraries(zmc zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS})
|
||||||
|
target_link_libraries(zmu zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS} bcrypt)
|
||||||
|
target_link_libraries(zms zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS} bcrypt)
|
||||||
|
|
||||||
# Generate man files for the binaries destined for the bin folder
|
# Generate man files for the binaries destined for the bin folder
|
||||||
FOREACH(CBINARY zmc zmu)
|
FOREACH(CBINARY zmc zmu)
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include "include/jwt-cpp/base.h"
|
||||||
|
|
||||||
|
TEST(BaseTest, Base64Decode) {
|
||||||
|
ASSERT_EQ("1", jwt::base::decode<jwt::alphabet::base64>("MQ=="));
|
||||||
|
ASSERT_EQ("12", jwt::base::decode<jwt::alphabet::base64>("MTI="));
|
||||||
|
ASSERT_EQ("123", jwt::base::decode<jwt::alphabet::base64>("MTIz"));
|
||||||
|
ASSERT_EQ("1234", jwt::base::decode<jwt::alphabet::base64>("MTIzNA=="));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(BaseTest, Base64DecodeURL) {
|
||||||
|
ASSERT_EQ("1", jwt::base::decode<jwt::alphabet::base64url>("MQ%3d%3d"));
|
||||||
|
ASSERT_EQ("12", jwt::base::decode<jwt::alphabet::base64url>("MTI%3d"));
|
||||||
|
ASSERT_EQ("123", jwt::base::decode<jwt::alphabet::base64url>("MTIz"));
|
||||||
|
ASSERT_EQ("1234", jwt::base::decode<jwt::alphabet::base64url>("MTIzNA%3d%3d"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(BaseTest, Base64Encode) {
|
||||||
|
ASSERT_EQ("MQ==", jwt::base::encode<jwt::alphabet::base64>("1"));
|
||||||
|
ASSERT_EQ("MTI=", jwt::base::encode<jwt::alphabet::base64>("12"));
|
||||||
|
ASSERT_EQ("MTIz", jwt::base::encode<jwt::alphabet::base64>("123"));
|
||||||
|
ASSERT_EQ("MTIzNA==", jwt::base::encode<jwt::alphabet::base64>("1234"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(BaseTest, Base64EncodeURL) {
|
||||||
|
ASSERT_EQ("MQ%3d%3d", jwt::base::encode<jwt::alphabet::base64url>("1"));
|
||||||
|
ASSERT_EQ("MTI%3d", jwt::base::encode<jwt::alphabet::base64url>("12"));
|
||||||
|
ASSERT_EQ("MTIz", jwt::base::encode<jwt::alphabet::base64url>("123"));
|
||||||
|
ASSERT_EQ("MTIzNA%3d%3d", jwt::base::encode<jwt::alphabet::base64url>("1234"));
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include "include/jwt-cpp/jwt.h"
|
||||||
|
|
||||||
|
TEST(ClaimTest, AudienceAsString) {
|
||||||
|
std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0.WZnM3SIiSRHsbO3O7Z2bmIzTJ4EC32HRBKfLznHhrh4";
|
||||||
|
auto decoded = jwt::decode(token);
|
||||||
|
|
||||||
|
ASSERT_TRUE(decoded.has_algorithm());
|
||||||
|
ASSERT_TRUE(decoded.has_type());
|
||||||
|
ASSERT_FALSE(decoded.has_content_type());
|
||||||
|
ASSERT_FALSE(decoded.has_key_id());
|
||||||
|
ASSERT_FALSE(decoded.has_issuer());
|
||||||
|
ASSERT_FALSE(decoded.has_subject());
|
||||||
|
ASSERT_TRUE(decoded.has_audience());
|
||||||
|
ASSERT_FALSE(decoded.has_expires_at());
|
||||||
|
ASSERT_FALSE(decoded.has_not_before());
|
||||||
|
ASSERT_FALSE(decoded.has_issued_at());
|
||||||
|
ASSERT_FALSE(decoded.has_id());
|
||||||
|
|
||||||
|
ASSERT_EQ("HS256", decoded.get_algorithm());
|
||||||
|
ASSERT_EQ("JWT", decoded.get_type());
|
||||||
|
auto aud = decoded.get_audience();
|
||||||
|
ASSERT_EQ(1, aud.size());
|
||||||
|
ASSERT_EQ("test", *aud.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ClaimTest, SetAudienceAsString) {
|
||||||
|
auto token = jwt::create()
|
||||||
|
.set_type("JWT")
|
||||||
|
.set_audience("test")
|
||||||
|
.sign(jwt::algorithm::hs256("test"));
|
||||||
|
ASSERT_EQ("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0.ny5Fa0vzAg7tNL95KWg_ecBNd3XP3tdAzq0SFA6diY4", token);
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,55 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include "include/jwt-cpp/jwt.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
extern std::string google_cert;
|
||||||
|
extern std::string google_cert_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(HelperTest, Cert2Pubkey) {
|
||||||
|
auto key = jwt::helper::extract_pubkey_from_cert(google_cert);
|
||||||
|
ASSERT_EQ(google_cert_key, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::string google_cert = R"(-----BEGIN CERTIFICATE-----
|
||||||
|
MIIF8DCCBVmgAwIBAgIKYFOB9QABAACIvTANBgkqhkiG9w0BAQUFADBGMQswCQYD
|
||||||
|
VQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZR29vZ2xlIElu
|
||||||
|
dGVybmV0IEF1dGhvcml0eTAeFw0xMzA1MjIxNTQ5MDRaFw0xMzEwMzEyMzU5NTla
|
||||||
|
MGYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N
|
||||||
|
b3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRUwEwYDVQQDFAwqLmdv
|
||||||
|
b2dsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARmSpIUbCqhUBq1UwnR
|
||||||
|
Ai7/TNSk6W8JmasR+I0r/NLDYv5yApbAz8HXXN8hDdurMRP6Jy1Q0UIKmyls8HPH
|
||||||
|
exoCo4IECjCCBAYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAsGA1Ud
|
||||||
|
DwQEAwIHgDAdBgNVHQ4EFgQUU3jT0NVNRgU5ZinRHGrlyoGEnoYwHwYDVR0jBBgw
|
||||||
|
FoAUv8Aw6/VDET5nup6R+/xq2uNrEiQwWwYDVR0fBFQwUjBQoE6gTIZKaHR0cDov
|
||||||
|
L3d3dy5nc3RhdGljLmNvbS9Hb29nbGVJbnRlcm5ldEF1dGhvcml0eS9Hb29nbGVJ
|
||||||
|
bnRlcm5ldEF1dGhvcml0eS5jcmwwZgYIKwYBBQUHAQEEWjBYMFYGCCsGAQUFBzAC
|
||||||
|
hkpodHRwOi8vd3d3LmdzdGF0aWMuY29tL0dvb2dsZUludGVybmV0QXV0aG9yaXR5
|
||||||
|
L0dvb2dsZUludGVybmV0QXV0aG9yaXR5LmNydDAMBgNVHRMBAf8EAjAAMIICwwYD
|
||||||
|
VR0RBIICujCCAraCDCouZ29vZ2xlLmNvbYINKi5hbmRyb2lkLmNvbYIWKi5hcHBl
|
||||||
|
bmdpbmUuZ29vZ2xlLmNvbYISKi5jbG91ZC5nb29nbGUuY29tghYqLmdvb2dsZS1h
|
||||||
|
bmFseXRpY3MuY29tggsqLmdvb2dsZS5jYYILKi5nb29nbGUuY2yCDiouZ29vZ2xl
|
||||||
|
LmNvLmlugg4qLmdvb2dsZS5jby5qcIIOKi5nb29nbGUuY28udWuCDyouZ29vZ2xl
|
||||||
|
LmNvbS5hcoIPKi5nb29nbGUuY29tLmF1gg8qLmdvb2dsZS5jb20uYnKCDyouZ29v
|
||||||
|
Z2xlLmNvbS5jb4IPKi5nb29nbGUuY29tLm14gg8qLmdvb2dsZS5jb20udHKCDyou
|
||||||
|
Z29vZ2xlLmNvbS52boILKi5nb29nbGUuZGWCCyouZ29vZ2xlLmVzggsqLmdvb2ds
|
||||||
|
ZS5mcoILKi5nb29nbGUuaHWCCyouZ29vZ2xlLml0ggsqLmdvb2dsZS5ubIILKi5n
|
||||||
|
b29nbGUucGyCCyouZ29vZ2xlLnB0gg8qLmdvb2dsZWFwaXMuY26CFCouZ29vZ2xl
|
||||||
|
Y29tbWVyY2UuY29tgg0qLmdzdGF0aWMuY29tggwqLnVyY2hpbi5jb22CECoudXJs
|
||||||
|
Lmdvb2dsZS5jb22CFioueW91dHViZS1ub2Nvb2tpZS5jb22CDSoueW91dHViZS5j
|
||||||
|
b22CFioueW91dHViZWVkdWNhdGlvbi5jb22CCyoueXRpbWcuY29tggthbmRyb2lk
|
||||||
|
LmNvbYIEZy5jb4IGZ29vLmdsghRnb29nbGUtYW5hbHl0aWNzLmNvbYIKZ29vZ2xl
|
||||||
|
LmNvbYISZ29vZ2xlY29tbWVyY2UuY29tggp1cmNoaW4uY29tggh5b3V0dS5iZYIL
|
||||||
|
eW91dHViZS5jb22CFHlvdXR1YmVlZHVjYXRpb24uY29tMA0GCSqGSIb3DQEBBQUA
|
||||||
|
A4GBAAMn0K3j3yhC+X+uyh6eABa2Eq7xiY5/mUB886Ir19vxluSMNKD6n/iY8vHj
|
||||||
|
trn0BhuW8/vmJyudFkIcEDUYE4ivQMlsfIL7SOGw6OevVLmm02aiRHWj5T20Ds+S
|
||||||
|
OpueYUG3NBcHP/5IzhUYIQJbGzlQaUaZBMaQeC8ZslMNLWI2
|
||||||
|
-----END CERTIFICATE-----)";
|
||||||
|
|
||||||
|
std::string google_cert_key = R"(-----BEGIN PUBLIC KEY-----
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZkqSFGwqoVAatVMJ0QIu/0zUpOlv
|
||||||
|
CZmrEfiNK/zSw2L+cgKWwM/B11zfIQ3bqzET+ictUNFCCpspbPBzx3saAg==
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
)";
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Dominik Thalhammer
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,98 @@
|
||||||
|
# jwt-cpp
|
||||||
|
|
||||||
|
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/5f7055e294744901991fd0a1620b231d)](https://app.codacy.com/app/Thalhammer/jwt-cpp?utm_source=github.com&utm_medium=referral&utm_content=Thalhammer/jwt-cpp&utm_campaign=Badge_Grade_Settings)
|
||||||
|
|
||||||
|
A header only library for creating and validating json web tokens in c++.
|
||||||
|
|
||||||
|
## Signature algorithms
|
||||||
|
As of version 0.2.0 jwt-cpp supports all algorithms defined by the spec. The modular design of jwt-cpp allows one to add additional algorithms without any problems. If you need any feel free to open a pull request.
|
||||||
|
For the sake of completeness, here is a list of all supported algorithms:
|
||||||
|
* HS256
|
||||||
|
* HS384
|
||||||
|
* HS512
|
||||||
|
* RS256
|
||||||
|
* RS384
|
||||||
|
* RS512
|
||||||
|
* ES256
|
||||||
|
* ES384
|
||||||
|
* ES512
|
||||||
|
* PS256
|
||||||
|
* PS384
|
||||||
|
* PS512
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
Simple example of decoding a token and printing all claims:
|
||||||
|
```c++
|
||||||
|
#include <jwt-cpp/jwt.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main(int argc, const char** argv) {
|
||||||
|
std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
|
||||||
|
auto decoded = jwt::decode(token);
|
||||||
|
|
||||||
|
for(auto& e : decoded.get_payload_claims())
|
||||||
|
std::cout << e.first << " = " << e.second.to_json() << std::endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In order to verify a token you first build a verifier and use it to verify a decoded token.
|
||||||
|
```c++
|
||||||
|
auto verifier = jwt::verify()
|
||||||
|
.allow_algorithm(jwt::algorithm::hs256{ "secret" })
|
||||||
|
.with_issuer("auth0");
|
||||||
|
|
||||||
|
verifier.verify(decoded_token);
|
||||||
|
```
|
||||||
|
The created verifier is stateless so you can reuse it for different tokens.
|
||||||
|
|
||||||
|
Creating a token (and signing) is equally easy.
|
||||||
|
```c++
|
||||||
|
auto token = jwt::create()
|
||||||
|
.set_issuer("auth0")
|
||||||
|
.set_type("JWS")
|
||||||
|
.set_payload_claim("sample", std::string("test"))
|
||||||
|
.sign(jwt::algorithm::hs256{"secret"});
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is a simple example of creating a token that will expire in 2 hours:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
|
||||||
|
// Note to @Thalhammer: please replace with a better example if this is not a good way
|
||||||
|
auto token = jwt::create()
|
||||||
|
.set_issuer("auth0")
|
||||||
|
.set_issued_at(jwt::date(std::chrono::system_clock::now()))
|
||||||
|
.set_expires_at(jwt::date(std::chrono::system_clock::now()+ std::chrono::seconds{3600}))
|
||||||
|
.sign(jwt::algorithm::hs256{"secret"}
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
If you have an improvement or found a bug feel free to [open an issue](https://github.com/Thalhammer/jwt-cpp/issues/new) or add the change and create a pull request. If you file a bug please make sure to include as much information about your environment (compiler version, etc.) as possible to help reproduce the issue. If you add a new feature please make sure to also include test cases for it.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
In order to use jwt-cpp you need the following tools.
|
||||||
|
* libcrypto (openssl or compatible)
|
||||||
|
* libssl-dev (for the header files)
|
||||||
|
* a compiler supporting at least c++11
|
||||||
|
* basic stl support
|
||||||
|
|
||||||
|
In order to build the test cases you also need
|
||||||
|
* gtest installed in linker path
|
||||||
|
* pthread
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
#### Expired tokens
|
||||||
|
If you are generating tokens that seem to immediately expire, you are likely not using UTC. Specifically,
|
||||||
|
if you use `get_time` to get the current time, it likely uses localtime, while this library uses UTC, which may be why your token is immediately expiring. Please see example above on the right way to use current time.
|
||||||
|
|
||||||
|
#### Missing _HMAC amd _EVP_sha256 symbols on Mac
|
||||||
|
There seems to exists a problem with the included openssl library of MacOS. Make sure you link to one provided by brew.
|
||||||
|
See [here](https://github.com/Thalhammer/jwt-cpp/issues/6) for more details.
|
||||||
|
#### Building on windows fails with syntax errors
|
||||||
|
The header "Windows.h", which is often included in windowsprojects, defines macros for MIN and MAX which screw up std::numeric_limits.
|
||||||
|
See [here](https://github.com/Thalhammer/jwt-cpp/issues/5) for more details. To fix this do one of the following things:
|
||||||
|
* define NOMINMAX, which suppresses this behaviour
|
||||||
|
* include this library before you include windows.h
|
||||||
|
* place ```#undef max``` and ```#undef min``` before you include this library
|
|
@ -0,0 +1,7 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include "include/jwt-cpp/jwt.h"
|
||||||
|
|
||||||
|
TEST(TokenFormatTest, MissingDot) {
|
||||||
|
ASSERT_THROW(jwt::decode("eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0.eyJpc3MiOiJhdXRoMCJ9"), std::invalid_argument);
|
||||||
|
ASSERT_THROW(jwt::decode("eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0eyJpc3MiOiJhdXRoMCJ9."), std::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenFormatTest, InvalidChar) {
|
||||||
|
ASSERT_THROW(jwt::decode("eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0().eyJpc3MiOiJhdXRoMCJ9."), std::runtime_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenFormatTest, InvalidJSON) {
|
||||||
|
ASSERT_THROW(jwt::decode("YXsiYWxnIjoibm9uZSIsInR5cCI6IkpXUyJ9YQ.eyJpc3MiOiJhdXRoMCJ9."), std::runtime_error);
|
||||||
|
}
|
|
@ -0,0 +1,420 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include "include/jwt-cpp/jwt.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
extern std::string rsa_priv_key;
|
||||||
|
extern std::string rsa_pub_key;
|
||||||
|
extern std::string rsa_pub_key_invalid;
|
||||||
|
extern std::string rsa512_priv_key;
|
||||||
|
extern std::string rsa512_pub_key;
|
||||||
|
extern std::string rsa512_pub_key_invalid;
|
||||||
|
extern std::string ecdsa_priv_key;
|
||||||
|
extern std::string ecdsa_pub_key;
|
||||||
|
extern std::string ecdsa_pub_key_invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, DecodeToken) {
|
||||||
|
std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
|
||||||
|
auto decoded = jwt::decode(token);
|
||||||
|
|
||||||
|
ASSERT_TRUE(decoded.has_algorithm());
|
||||||
|
ASSERT_TRUE(decoded.has_type());
|
||||||
|
ASSERT_FALSE(decoded.has_content_type());
|
||||||
|
ASSERT_FALSE(decoded.has_key_id());
|
||||||
|
ASSERT_TRUE(decoded.has_issuer());
|
||||||
|
ASSERT_FALSE(decoded.has_subject());
|
||||||
|
ASSERT_FALSE(decoded.has_audience());
|
||||||
|
ASSERT_FALSE(decoded.has_expires_at());
|
||||||
|
ASSERT_FALSE(decoded.has_not_before());
|
||||||
|
ASSERT_FALSE(decoded.has_issued_at());
|
||||||
|
ASSERT_FALSE(decoded.has_id());
|
||||||
|
|
||||||
|
ASSERT_EQ("HS256", decoded.get_algorithm());
|
||||||
|
ASSERT_EQ("JWS", decoded.get_type());
|
||||||
|
ASSERT_EQ("auth0", decoded.get_issuer());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, CreateToken) {
|
||||||
|
auto token = jwt::create()
|
||||||
|
.set_issuer("auth0")
|
||||||
|
.set_type("JWS")
|
||||||
|
.sign(jwt::algorithm::none{});
|
||||||
|
ASSERT_EQ("eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0.eyJpc3MiOiJhdXRoMCJ9.", token);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, CreateTokenHS256) {
|
||||||
|
auto token = jwt::create()
|
||||||
|
.set_issuer("auth0")
|
||||||
|
.set_type("JWS")
|
||||||
|
.sign(jwt::algorithm::hs256{"secret"});
|
||||||
|
ASSERT_EQ("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE", token);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, CreateTokenRS256) {
|
||||||
|
auto token = jwt::create()
|
||||||
|
.set_issuer("auth0")
|
||||||
|
.set_type("JWS")
|
||||||
|
.sign(jwt::algorithm::rs256(rsa_pub_key, rsa_priv_key, "", ""));
|
||||||
|
|
||||||
|
ASSERT_EQ(
|
||||||
|
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8"
|
||||||
|
"HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4"
|
||||||
|
"YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9"
|
||||||
|
"sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-uc2z5c05CCXqVSpfCjWbh9gQ", token);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, CreateTokenRS512) {
|
||||||
|
auto token = jwt::create()
|
||||||
|
.set_issuer("auth0")
|
||||||
|
.set_type("JWS")
|
||||||
|
.sign(jwt::algorithm::rs512(rsa512_pub_key, rsa512_priv_key, "", ""));
|
||||||
|
|
||||||
|
ASSERT_EQ(
|
||||||
|
"eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.GZhnjtsvBl2_KDSxg4JW6xnmNjr2mWhYSZSSQyLKvI0"
|
||||||
|
"TK86sJKchkt_HDy2IC5l5BGRhq_Xv9pHdA1umidQZG3a7gWvHsujqybCBgBraMTd1wJrCl4QxFg2RYHhHbRqb9BnPJgFD_vryd4GB"
|
||||||
|
"hfGgejPBCBlGrQtqFGFdHHOjNHY", token);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, CreateTokenPS256) {
|
||||||
|
auto token = jwt::create()
|
||||||
|
.set_issuer("auth0")
|
||||||
|
.set_type("JWS")
|
||||||
|
.sign(jwt::algorithm::ps256(rsa_pub_key, rsa_priv_key, "", ""));
|
||||||
|
|
||||||
|
// TODO: Find a better way to check if generated signature is valid
|
||||||
|
// Can't do simple check for equal since pss adds random salt.
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, CreateTokenPS384) {
|
||||||
|
auto token = jwt::create()
|
||||||
|
.set_issuer("auth0")
|
||||||
|
.set_type("JWS")
|
||||||
|
.sign(jwt::algorithm::ps384(rsa_pub_key, rsa_priv_key, "", ""));
|
||||||
|
|
||||||
|
// TODO: Find a better way to check if generated signature is valid
|
||||||
|
// Can't do simple check for equal since pss adds random salt.
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, CreateTokenPS512) {
|
||||||
|
auto token = jwt::create()
|
||||||
|
.set_issuer("auth0")
|
||||||
|
.set_type("JWS")
|
||||||
|
.sign(jwt::algorithm::ps512(rsa_pub_key, rsa_priv_key, "", ""));
|
||||||
|
|
||||||
|
// TODO: Find a better way to check if generated signature is valid
|
||||||
|
// Can't do simple check for equal since pss adds random salt.
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, CreateTokenES256) {
|
||||||
|
|
||||||
|
auto token = jwt::create()
|
||||||
|
.set_issuer("auth0")
|
||||||
|
.set_type("JWS")
|
||||||
|
.sign(jwt::algorithm::es256("", ecdsa_priv_key, "", ""));
|
||||||
|
|
||||||
|
auto decoded = jwt::decode(token);
|
||||||
|
|
||||||
|
ASSERT_THROW(jwt::verify().allow_algorithm(jwt::algorithm::es256(ecdsa_pub_key_invalid, "", "", "")).verify(decoded), jwt::signature_verification_exception);
|
||||||
|
ASSERT_NO_THROW(jwt::verify().allow_algorithm(jwt::algorithm::es256(ecdsa_pub_key, "", "", "")).verify(decoded));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, CreateTokenES256NoPrivate) {
|
||||||
|
|
||||||
|
ASSERT_THROW([](){
|
||||||
|
auto token = jwt::create()
|
||||||
|
.set_issuer("auth0")
|
||||||
|
.set_type("JWS")
|
||||||
|
.sign(jwt::algorithm::es256(ecdsa_pub_key, "", "", ""));
|
||||||
|
}(), jwt::signature_generation_exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, VerifyTokenRS256) {
|
||||||
|
std::string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8"
|
||||||
|
"HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4"
|
||||||
|
"YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9"
|
||||||
|
"sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-uc2z5c05CCXqVSpfCjWbh9gQ";
|
||||||
|
|
||||||
|
auto verify = jwt::verify()
|
||||||
|
.allow_algorithm(jwt::algorithm::rs256(rsa_pub_key, rsa_priv_key, "", ""))
|
||||||
|
.with_issuer("auth0");
|
||||||
|
|
||||||
|
auto decoded_token = jwt::decode(token);
|
||||||
|
|
||||||
|
verify.verify(decoded_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, VerifyTokenRS256PublicOnly) {
|
||||||
|
std::string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8"
|
||||||
|
"HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4"
|
||||||
|
"YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9"
|
||||||
|
"sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-uc2z5c05CCXqVSpfCjWbh9gQ";
|
||||||
|
|
||||||
|
auto verify = jwt::verify()
|
||||||
|
.allow_algorithm(jwt::algorithm::rs256(rsa_pub_key, "", "", ""))
|
||||||
|
.with_issuer("auth0");
|
||||||
|
|
||||||
|
auto decoded_token = jwt::decode(token);
|
||||||
|
|
||||||
|
verify.verify(decoded_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, VerifyTokenRS256Fail) {
|
||||||
|
std::string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8"
|
||||||
|
"HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4"
|
||||||
|
"YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9"
|
||||||
|
"sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-uc2z5c05CCXqVSpfCjWbh9gQ";
|
||||||
|
|
||||||
|
auto verify = jwt::verify()
|
||||||
|
.allow_algorithm(jwt::algorithm::rs256(rsa_pub_key_invalid, "", "", ""))
|
||||||
|
.with_issuer("auth0");
|
||||||
|
|
||||||
|
auto decoded_token = jwt::decode(token);
|
||||||
|
|
||||||
|
ASSERT_THROW(verify.verify(decoded_token), jwt::signature_verification_exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, VerifyTokenRS512) {
|
||||||
|
std::string token = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.GZhnjtsvBl2_KDSxg4JW6xnmNjr2mWhYSZ"
|
||||||
|
"SSQyLKvI0TK86sJKchkt_HDy2IC5l5BGRhq_Xv9pHdA1umidQZG3a7gWvHsujqybCBgBraMTd1wJrCl4QxFg2RYHhHbRqb9BnPJgFD_vryd4"
|
||||||
|
"GBhfGgejPBCBlGrQtqFGFdHHOjNHY";
|
||||||
|
|
||||||
|
auto verify = jwt::verify()
|
||||||
|
.allow_algorithm(jwt::algorithm::rs512(rsa512_pub_key, rsa512_priv_key, "", ""))
|
||||||
|
.with_issuer("auth0");
|
||||||
|
|
||||||
|
auto decoded_token = jwt::decode(token);
|
||||||
|
|
||||||
|
verify.verify(decoded_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, VerifyTokenRS512PublicOnly) {
|
||||||
|
std::string token = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.GZhnjtsvBl2_KDSxg4JW6xnmNjr2mWhYSZ"
|
||||||
|
"SSQyLKvI0TK86sJKchkt_HDy2IC5l5BGRhq_Xv9pHdA1umidQZG3a7gWvHsujqybCBgBraMTd1wJrCl4QxFg2RYHhHbRqb9BnPJgFD_vryd4"
|
||||||
|
"GBhfGgejPBCBlGrQtqFGFdHHOjNHY";
|
||||||
|
|
||||||
|
auto verify = jwt::verify()
|
||||||
|
.allow_algorithm(jwt::algorithm::rs512(rsa512_pub_key, "", "", ""))
|
||||||
|
.with_issuer("auth0");
|
||||||
|
|
||||||
|
auto decoded_token = jwt::decode(token);
|
||||||
|
|
||||||
|
verify.verify(decoded_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, VerifyTokenRS512Fail) {
|
||||||
|
std::string token = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.GZhnjtsvBl2_KDSxg4JW6xnmNjr2mWhYSZ"
|
||||||
|
"SSQyLKvI0TK86sJKchkt_HDy2IC5l5BGRhq_Xv9pHdA1umidQZG3a7gWvHsujqybCBgBraMTd1wJrCl4QxFg2RYHhHbRqb9BnPJgFD_vryd4"
|
||||||
|
"GBhfGgejPBCBlGrQtqFGFdHHOjNHY";
|
||||||
|
|
||||||
|
auto verify = jwt::verify()
|
||||||
|
.allow_algorithm(jwt::algorithm::rs512(rsa_pub_key_invalid, "", "", ""))
|
||||||
|
.with_issuer("auth0");
|
||||||
|
|
||||||
|
auto decoded_token = jwt::decode(token);
|
||||||
|
|
||||||
|
ASSERT_THROW(verify.verify(decoded_token), jwt::signature_verification_exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, VerifyTokenHS256) {
|
||||||
|
std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
|
||||||
|
|
||||||
|
auto verify = jwt::verify()
|
||||||
|
.allow_algorithm(jwt::algorithm::hs256{ "secret" })
|
||||||
|
.with_issuer("auth0");
|
||||||
|
|
||||||
|
auto decoded_token = jwt::decode(token);
|
||||||
|
verify.verify(decoded_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, VerifyFail) {
|
||||||
|
auto token = jwt::create()
|
||||||
|
.set_issuer("auth0")
|
||||||
|
.set_type("JWS")
|
||||||
|
.sign(jwt::algorithm::none{});
|
||||||
|
|
||||||
|
auto decoded_token = jwt::decode(token);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto verify = jwt::verify()
|
||||||
|
.allow_algorithm(jwt::algorithm::none{})
|
||||||
|
.with_issuer("auth");
|
||||||
|
ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto verify = jwt::verify()
|
||||||
|
.allow_algorithm(jwt::algorithm::none{})
|
||||||
|
.with_issuer("auth0")
|
||||||
|
.with_audience({ "test" });
|
||||||
|
ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto verify = jwt::verify()
|
||||||
|
.allow_algorithm(jwt::algorithm::none{})
|
||||||
|
.with_issuer("auth0")
|
||||||
|
.with_subject("test");
|
||||||
|
ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto verify = jwt::verify()
|
||||||
|
.allow_algorithm(jwt::algorithm::none{})
|
||||||
|
.with_issuer("auth0")
|
||||||
|
.with_claim("myclaim", jwt::claim(std::string("test")));
|
||||||
|
ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, VerifyTokenES256) {
|
||||||
|
const std::string token = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1g";
|
||||||
|
|
||||||
|
auto verify = jwt::verify().allow_algorithm(jwt::algorithm::es256(ecdsa_pub_key, "", "", ""));
|
||||||
|
auto decoded_token = jwt::decode(token);
|
||||||
|
|
||||||
|
verify.verify(decoded_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, VerifyTokenES256Fail) {
|
||||||
|
const std::string token = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1g";
|
||||||
|
|
||||||
|
auto verify = jwt::verify()
|
||||||
|
.allow_algorithm(jwt::algorithm::es256(ecdsa_pub_key_invalid, "", "", ""));
|
||||||
|
auto decoded_token = jwt::decode(token);
|
||||||
|
|
||||||
|
ASSERT_THROW(verify.verify(decoded_token), jwt::signature_verification_exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, VerifyTokenPS256) {
|
||||||
|
std::string token = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.CJ4XjVWdbV6vXGZkD4GdJbtYc80SN9cmPOqRhZBRzOyDRqTFE"
|
||||||
|
"4MsbdKyQuhAWcvuMOjn-24qOTjVMR_P_uTC1uG6WPLcucxZyLnbb56zbKnEklW2SX0mQnCGewr-93a_vDaFT6Cp45MsF_OwFPRCMaS5CJg-"
|
||||||
|
"N5KY67UrVSr3s9nkuK9ZTQkyODHfyEUh9F_FhRCATGrb5G7_qHqBYvTvaPUXqzhhpCjN855Tocg7A24Hl0yMwM-XdasucW5xNdKjG_YCkis"
|
||||||
|
"HX7ax--JiF5GNYCO61eLFteO4THUg-3Z0r4OlGqlppyWo5X5tjcxOZCvBh7WDWfkxA48KFZPRv0nlKA";
|
||||||
|
|
||||||
|
auto verify = jwt::verify()
|
||||||
|
.allow_algorithm(jwt::algorithm::ps256(rsa_pub_key, rsa_priv_key, "", ""))
|
||||||
|
.with_issuer("auth0");
|
||||||
|
|
||||||
|
auto decoded_token = jwt::decode(token);
|
||||||
|
|
||||||
|
verify.verify(decoded_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, VerifyTokenPS256PublicOnly) {
|
||||||
|
std::string token = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.CJ4XjVWdbV6vXGZkD4GdJbtYc80SN9cmPOqRhZBRzOyDRqTFE"
|
||||||
|
"4MsbdKyQuhAWcvuMOjn-24qOTjVMR_P_uTC1uG6WPLcucxZyLnbb56zbKnEklW2SX0mQnCGewr-93a_vDaFT6Cp45MsF_OwFPRCMaS5CJg-"
|
||||||
|
"N5KY67UrVSr3s9nkuK9ZTQkyODHfyEUh9F_FhRCATGrb5G7_qHqBYvTvaPUXqzhhpCjN855Tocg7A24Hl0yMwM-XdasucW5xNdKjG_YCkis"
|
||||||
|
"HX7ax--JiF5GNYCO61eLFteO4THUg-3Z0r4OlGqlppyWo5X5tjcxOZCvBh7WDWfkxA48KFZPRv0nlKA";
|
||||||
|
|
||||||
|
auto verify = jwt::verify()
|
||||||
|
.allow_algorithm(jwt::algorithm::ps256(rsa_pub_key, "", "", ""))
|
||||||
|
.with_issuer("auth0");
|
||||||
|
|
||||||
|
auto decoded_token = jwt::decode(token);
|
||||||
|
|
||||||
|
verify.verify(decoded_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TokenTest, VerifyTokenPS256Fail) {
|
||||||
|
std::string token = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.CJ4XjVWdbV6vXGZkD4GdJbtYc80SN9cmPOqRhZBRzOyDRqTFE"
|
||||||
|
"4MsbdKyQuhAWcvuMOjn-24qOTjVMR_P_uTC1uG6WPLcucxZyLnbb56zbKnEklW2SX0mQnCGewr-93a_vDaFT6Cp45MsF_OwFPRCMaS5CJg-"
|
||||||
|
"N5KY67UrVSr3s9nkuK9ZTQkyODHfyEUh9F_FhRCATGrb5G7_qHqBYvTvaPUXqzhhpCjN855Tocg7A24Hl0yMwM-XdasucW5xNdKjG_YCkis"
|
||||||
|
"HX7ax--JiF5GNYCO61eLFteO4THUg-3Z0r4OlGqlppyWo5X5tjcxOZCvBh7WDWfkxA48KFZPRv0nlKA";
|
||||||
|
|
||||||
|
auto verify = jwt::verify()
|
||||||
|
.allow_algorithm(jwt::algorithm::ps256(rsa_pub_key_invalid, "", "", ""))
|
||||||
|
.with_issuer("auth0");
|
||||||
|
|
||||||
|
auto decoded_token = jwt::decode(token);
|
||||||
|
|
||||||
|
ASSERT_THROW(verify.verify(decoded_token), jwt::signature_verification_exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::string rsa_priv_key = R"(-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC4ZtdaIrd1BPIJ
|
||||||
|
tfnF0TjIK5inQAXZ3XlCrUlJdP+XHwIRxdv1FsN12XyMYO/6ymLmo9ryoQeIrsXB
|
||||||
|
XYqlET3zfAY+diwCb0HEsVvhisthwMU4gZQu6TYW2s9LnXZB5rVtcBK69hcSlA2k
|
||||||
|
ZudMZWxZcj0L7KMfO2rIvaHw/qaVOE9j0T257Z8Kp2CLF9MUgX0ObhIsdumFRLaL
|
||||||
|
DvDUmBPr2zuh/34j2XmWwn1yjN/WvGtdfhXW79Ki1S40HcWnygHgLV8sESFKUxxQ
|
||||||
|
mKvPUTwDOIwLFL5WtE8Mz7N++kgmDcmWMCHc8kcOIu73Ta/3D4imW7VbKgHZo9+K
|
||||||
|
3ESFE3RjAgMBAAECggEBAJTEIyjMqUT24G2FKiS1TiHvShBkTlQdoR5xvpZMlYbN
|
||||||
|
tVWxUmrAGqCQ/TIjYnfpnzCDMLhdwT48Ab6mQJw69MfiXwc1PvwX1e9hRscGul36
|
||||||
|
ryGPKIVQEBsQG/zc4/L2tZe8ut+qeaK7XuYrPp8bk/X1e9qK5m7j+JpKosNSLgJj
|
||||||
|
NIbYsBkG2Mlq671irKYj2hVZeaBQmWmZxK4fw0Istz2WfN5nUKUeJhTwpR+JLUg4
|
||||||
|
ELYYoB7EO0Cej9UBG30hbgu4RyXA+VbptJ+H042K5QJROUbtnLWuuWosZ5ATldwO
|
||||||
|
u03dIXL0SH0ao5NcWBzxU4F2sBXZRGP2x/jiSLHcqoECgYEA4qD7mXQpu1b8XO8U
|
||||||
|
6abpKloJCatSAHzjgdR2eRDRx5PMvloipfwqA77pnbjTUFajqWQgOXsDTCjcdQui
|
||||||
|
wf5XAaWu+TeAVTytLQbSiTsBhrnoqVrr3RoyDQmdnwHT8aCMouOgcC5thP9vQ8Us
|
||||||
|
rVdjvRRbnJpg3BeSNimH+u9AHgsCgYEA0EzcbOltCWPHRAY7B3Ge/AKBjBQr86Kv
|
||||||
|
TdpTlxePBDVIlH+BM6oct2gaSZZoHbqPjbq5v7yf0fKVcXE4bSVgqfDJ/sZQu9Lp
|
||||||
|
PTeV7wkk0OsAMKk7QukEpPno5q6tOTNnFecpUhVLLlqbfqkB2baYYwLJR3IRzboJ
|
||||||
|
FQbLY93E8gkCgYB+zlC5VlQbbNqcLXJoImqItgQkkuW5PCgYdwcrSov2ve5r/Acz
|
||||||
|
FNt1aRdSlx4176R3nXyibQA1Vw+ztiUFowiP9WLoM3PtPZwwe4bGHmwGNHPIfwVG
|
||||||
|
m+exf9XgKKespYbLhc45tuC08DATnXoYK7O1EnUINSFJRS8cezSI5eHcbQKBgQDC
|
||||||
|
PgqHXZ2aVftqCc1eAaxaIRQhRmY+CgUjumaczRFGwVFveP9I6Gdi+Kca3DE3F9Pq
|
||||||
|
PKgejo0SwP5vDT+rOGHN14bmGJUMsX9i4MTmZUZ5s8s3lXh3ysfT+GAhTd6nKrIE
|
||||||
|
kM3Nh6HWFhROptfc6BNusRh1kX/cspDplK5x8EpJ0QKBgQDWFg6S2je0KtbV5PYe
|
||||||
|
RultUEe2C0jYMDQx+JYxbPmtcopvZQrFEur3WKVuLy5UAy7EBvwMnZwIG7OOohJb
|
||||||
|
vkSpADK6VPn9lbqq7O8cTedEHttm6otmLt8ZyEl3hZMaL3hbuRj6ysjmoFKx6CrX
|
||||||
|
rK0/Ikt5ybqUzKCMJZg2VKGTxg==
|
||||||
|
-----END PRIVATE KEY-----)";
|
||||||
|
std::string rsa_pub_key = R"(-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuGbXWiK3dQTyCbX5xdE4
|
||||||
|
yCuYp0AF2d15Qq1JSXT/lx8CEcXb9RbDddl8jGDv+spi5qPa8qEHiK7FwV2KpRE9
|
||||||
|
83wGPnYsAm9BxLFb4YrLYcDFOIGULuk2FtrPS512Qea1bXASuvYXEpQNpGbnTGVs
|
||||||
|
WXI9C+yjHztqyL2h8P6mlThPY9E9ue2fCqdgixfTFIF9Dm4SLHbphUS2iw7w1JgT
|
||||||
|
69s7of9+I9l5lsJ9cozf1rxrXX4V1u/SotUuNB3Fp8oB4C1fLBEhSlMcUJirz1E8
|
||||||
|
AziMCxS+VrRPDM+zfvpIJg3JljAh3PJHDiLu902v9w+Iplu1WyoB2aPfitxEhRN0
|
||||||
|
YwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----)";
|
||||||
|
std::string rsa_pub_key_invalid = R"(-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxzYuc22QSst/dS7geYYK
|
||||||
|
5l5kLxU0tayNdixkEQ17ix+CUcUbKIsnyftZxaCYT46rQtXgCaYRdJcbB3hmyrOa
|
||||||
|
vkhTpX79xJZnQmfuamMbZBqitvscxW9zRR9tBUL6vdi/0rpoUwPMEh8+Bw7CgYR0
|
||||||
|
FK0DhWYBNDfe9HKcyZEv3max8Cdq18htxjEsdYO0iwzhtKRXomBWTdhD5ykd/fAC
|
||||||
|
VTr4+KEY+IeLvubHVmLUhbE5NgWXxrRpGasDqzKhCTmsa2Ysf712rl57SlH0Wz/M
|
||||||
|
r3F7aM9YpErzeYLrl0GhQr9BVJxOvXcVd4kmY+XkiCcrkyS1cnghnllh+LCwQu1s
|
||||||
|
YwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----)";
|
||||||
|
std::string rsa512_priv_key = R"(-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw
|
||||||
|
33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW
|
||||||
|
+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB
|
||||||
|
AoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS
|
||||||
|
3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5Cp
|
||||||
|
uGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE
|
||||||
|
2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0
|
||||||
|
GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0K
|
||||||
|
Su5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY
|
||||||
|
6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5
|
||||||
|
fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523
|
||||||
|
Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP
|
||||||
|
FaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw==
|
||||||
|
-----END RSA PRIVATE KEY-----)";
|
||||||
|
std::string rsa512_pub_key = R"(-----BEGIN PUBLIC KEY-----
|
||||||
|
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd
|
||||||
|
UWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs
|
||||||
|
HUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D
|
||||||
|
o2kQ+X5xK9cipRgEKwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----)";
|
||||||
|
std::string rsa512_pub_key_invalid = R"(-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxzYuc22QSst/dS7geYYK
|
||||||
|
5l5kLxU0tayNdixkEQ17ix+CUcUbKIsnyftZxaCYT46rQtXgCaYRdJcbB3hmyrOa
|
||||||
|
vkhTpX79xJZnQmfuamMbZBqitvscxW9zRR9tBUL6vdi/0rpoUwPMEh8+Bw7CgYR0
|
||||||
|
FK0DhWYBNDfe9HKcyZEv3max8Cdq18htxjEsdYO0iwzhtKRXomBWTdhD5ykd/fAC
|
||||||
|
VTr4+KEY+IeLvubHVmLUhbE5NgWXxrRpGasDqzKhCTmsa2Ysf712rl57SlH0Wz/M
|
||||||
|
r3F7aM9YpErzeYLrl0GhQr9BVJxOvXcVd4kmY+XkiCcrkyS1cnghnllh+LCwQu1s
|
||||||
|
YwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----)";
|
||||||
|
std::string ecdsa_priv_key = R"(-----BEGIN PRIVATE KEY-----
|
||||||
|
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPGJGAm4X1fvBuC1z
|
||||||
|
SpO/4Izx6PXfNMaiKaS5RUkFqEGhRANCAARCBvmeksd3QGTrVs2eMrrfa7CYF+sX
|
||||||
|
sjyGg+Bo5mPKGH4Gs8M7oIvoP9pb/I85tdebtKlmiCZHAZE5w4DfJSV6
|
||||||
|
-----END PRIVATE KEY-----)";
|
||||||
|
std::string ecdsa_pub_key = R"(-----BEGIN PUBLIC KEY-----
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQgb5npLHd0Bk61bNnjK632uwmBfr
|
||||||
|
F7I8hoPgaOZjyhh+BrPDO6CL6D/aW/yPObXXm7SpZogmRwGROcOA3yUleg==
|
||||||
|
-----END PUBLIC KEY-----)";
|
||||||
|
std::string ecdsa_pub_key_invalid = R"(-----BEGIN PUBLIC KEY-----
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoBUyo8CQAFPeYPvv78ylh5MwFZjT
|
||||||
|
CLQeb042TjiMJxG+9DLFmRSMlBQ9T/RsLLc+PmpB1+7yPAR+oR5gZn3kJQ==
|
||||||
|
-----END PUBLIC KEY-----)";
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace jwt {
|
||||||
|
namespace alphabet {
|
||||||
|
struct base64 {
|
||||||
|
static const std::array<char, 64>& data() {
|
||||||
|
static std::array<char, 64> data = {
|
||||||
|
{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||||
|
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||||
|
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||||
|
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}};
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
static const std::string& fill() {
|
||||||
|
static std::string fill = "=";
|
||||||
|
return fill;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
struct base64url {
|
||||||
|
static const std::array<char, 64>& data() {
|
||||||
|
static std::array<char, 64> data = {
|
||||||
|
{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||||
|
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||||
|
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||||
|
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}};
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
static const std::string& fill() {
|
||||||
|
static std::string fill = "%3d";
|
||||||
|
return fill;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class base {
|
||||||
|
public:
|
||||||
|
template<typename T>
|
||||||
|
static std::string encode(const std::string& bin) {
|
||||||
|
return encode(bin, T::data(), T::fill());
|
||||||
|
}
|
||||||
|
template<typename T>
|
||||||
|
static std::string decode(const std::string& base) {
|
||||||
|
return decode(base, T::data(), T::fill());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::string encode(const std::string& bin, const std::array<char, 64>& alphabet, const std::string& fill) {
|
||||||
|
size_t size = bin.size();
|
||||||
|
std::string res;
|
||||||
|
|
||||||
|
// clear incomplete bytes
|
||||||
|
size_t fast_size = size - size % 3;
|
||||||
|
for (size_t i = 0; i < fast_size;) {
|
||||||
|
uint32_t octet_a = (unsigned char)bin[i++];
|
||||||
|
uint32_t octet_b = (unsigned char)bin[i++];
|
||||||
|
uint32_t octet_c = (unsigned char)bin[i++];
|
||||||
|
|
||||||
|
uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
|
||||||
|
|
||||||
|
res += alphabet[(triple >> 3 * 6) & 0x3F];
|
||||||
|
res += alphabet[(triple >> 2 * 6) & 0x3F];
|
||||||
|
res += alphabet[(triple >> 1 * 6) & 0x3F];
|
||||||
|
res += alphabet[(triple >> 0 * 6) & 0x3F];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fast_size == size)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
size_t mod = size % 3;
|
||||||
|
|
||||||
|
uint32_t octet_a = fast_size < size ? (unsigned char)bin[fast_size++] : 0;
|
||||||
|
uint32_t octet_b = fast_size < size ? (unsigned char)bin[fast_size++] : 0;
|
||||||
|
uint32_t octet_c = fast_size < size ? (unsigned char)bin[fast_size++] : 0;
|
||||||
|
|
||||||
|
uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
|
||||||
|
|
||||||
|
switch (mod) {
|
||||||
|
case 1:
|
||||||
|
res += alphabet[(triple >> 3 * 6) & 0x3F];
|
||||||
|
res += alphabet[(triple >> 2 * 6) & 0x3F];
|
||||||
|
res += fill;
|
||||||
|
res += fill;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
res += alphabet[(triple >> 3 * 6) & 0x3F];
|
||||||
|
res += alphabet[(triple >> 2 * 6) & 0x3F];
|
||||||
|
res += alphabet[(triple >> 1 * 6) & 0x3F];
|
||||||
|
res += fill;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string decode(const std::string& base, const std::array<char, 64>& alphabet, const std::string& fill) {
|
||||||
|
size_t size = base.size();
|
||||||
|
|
||||||
|
size_t fill_cnt = 0;
|
||||||
|
while (size > fill.size()) {
|
||||||
|
if (base.substr(size - fill.size(), fill.size()) == fill) {
|
||||||
|
fill_cnt++;
|
||||||
|
size -= fill.size();
|
||||||
|
if(fill_cnt > 2)
|
||||||
|
throw std::runtime_error("Invalid input");
|
||||||
|
}
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((size + fill_cnt) % 4 != 0)
|
||||||
|
throw std::runtime_error("Invalid input");
|
||||||
|
|
||||||
|
size_t out_size = size / 4 * 3;
|
||||||
|
std::string res;
|
||||||
|
res.reserve(out_size);
|
||||||
|
|
||||||
|
auto get_sextet = [&](size_t offset) {
|
||||||
|
for (size_t i = 0; i < alphabet.size(); i++) {
|
||||||
|
if (alphabet[i] == base[offset])
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Invalid input");
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
size_t fast_size = size - size % 4;
|
||||||
|
for (size_t i = 0; i < fast_size;) {
|
||||||
|
uint32_t sextet_a = get_sextet(i++);
|
||||||
|
uint32_t sextet_b = get_sextet(i++);
|
||||||
|
uint32_t sextet_c = get_sextet(i++);
|
||||||
|
uint32_t sextet_d = get_sextet(i++);
|
||||||
|
|
||||||
|
uint32_t triple = (sextet_a << 3 * 6)
|
||||||
|
+ (sextet_b << 2 * 6)
|
||||||
|
+ (sextet_c << 1 * 6)
|
||||||
|
+ (sextet_d << 0 * 6);
|
||||||
|
|
||||||
|
res += (triple >> 2 * 8) & 0xFF;
|
||||||
|
res += (triple >> 1 * 8) & 0xFF;
|
||||||
|
res += (triple >> 0 * 8) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fill_cnt == 0)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
uint32_t triple = (get_sextet(fast_size) << 3 * 6)
|
||||||
|
+ (get_sextet(fast_size + 1) << 2 * 6);
|
||||||
|
|
||||||
|
switch (fill_cnt) {
|
||||||
|
case 1:
|
||||||
|
triple |= (get_sextet(fast_size + 2) << 1 * 6);
|
||||||
|
res += (triple >> 2 * 8) & 0xFF;
|
||||||
|
res += (triple >> 1 * 8) & 0xFF;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
res += (triple >> 2 * 8) & 0xFF;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 14
|
||||||
|
VisualStudioVersion = 14.0.25420.1
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jwt-cpp", "jwt-cpp.vcxproj", "{1CA8C676-7F8E-434C-9069-8F20A562E6E9}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Debug|x86.ActiveCfg = Debug|Win32
|
||||||
|
{1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Debug|x86.Build.0 = Debug|Win32
|
||||||
|
{1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Release|x64.Build.0 = Release|x64
|
||||||
|
{1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Release|x86.ActiveCfg = Release|Win32
|
||||||
|
{1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Release|x86.Build.0 = Release|Win32
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
|
@ -0,0 +1,160 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
|
<ProjectConfiguration Include="Debug|Win32">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|Win32">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Debug|x64">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|x64">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectGuid>{1CA8C676-7F8E-434C-9069-8F20A562E6E9}</ProjectGuid>
|
||||||
|
<Keyword>Win32Proj</Keyword>
|
||||||
|
<RootNamespace>jwtcpp</RootNamespace>
|
||||||
|
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v140</PlatformToolset>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v140</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v140</PlatformToolset>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v140</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<ImportGroup Label="ExtensionSettings">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="Shared">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<LinkIncremental>true</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<LinkIncremental>true</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<LinkIncremental>false</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<LinkIncremental>false</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<PrecompiledHeader>
|
||||||
|
</PrecompiledHeader>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<Optimization>Disabled</Optimization>
|
||||||
|
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
<AdditionalDependencies>gtest.lib;gtest_main.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<PrecompiledHeader>
|
||||||
|
</PrecompiledHeader>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<Optimization>Disabled</Optimization>
|
||||||
|
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
<AdditionalDependencies>gtest.lib;gtest_main.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<PrecompiledHeader>
|
||||||
|
</PrecompiledHeader>
|
||||||
|
<Optimization>MaxSpeed</Optimization>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
<AdditionalDependencies>gtest.lib;gtest_main.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<PrecompiledHeader>
|
||||||
|
</PrecompiledHeader>
|
||||||
|
<Optimization>MaxSpeed</Optimization>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
<AdditionalDependencies>gtest.lib;gtest_main.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="include\jwt-cpp\base.h" />
|
||||||
|
<ClInclude Include="include\jwt-cpp\jwt.h" />
|
||||||
|
<ClInclude Include="include\jwt-cpp\picojson.h" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="BaseTest.cpp" />
|
||||||
|
<ClCompile Include="TokenTest.cpp" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
</ImportGroup>
|
||||||
|
</Project>
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup>
|
||||||
|
<Filter Include="Quelldateien">
|
||||||
|
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||||
|
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Headerdateien">
|
||||||
|
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||||
|
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Ressourcendateien">
|
||||||
|
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||||
|
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||||
|
</Filter>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="include\jwt-cpp\jwt.h">
|
||||||
|
<Filter>Headerdateien</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="include\jwt-cpp\picojson.h">
|
||||||
|
<Filter>Headerdateien</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="include\jwt-cpp\base.h">
|
||||||
|
<Filter>Headerdateien</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="BaseTest.cpp">
|
||||||
|
<Filter>Quelldateien</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="TokenTest.cpp">
|
||||||
|
<Filter>Quelldateien</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
|
@ -0,0 +1,3 @@
|
||||||
|
Source: jwt-cpp
|
||||||
|
Version: 2019-04-20
|
||||||
|
Description: A header only library for creating and validating json web tokens in c++
|
|
@ -0,0 +1,12 @@
|
||||||
|
diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h
|
||||||
|
index ec56810..a26fd97 100644
|
||||||
|
--- a/include/jwt-cpp/jwt.h
|
||||||
|
+++ b/include/jwt-cpp/jwt.h
|
||||||
|
@@ -1,6 +1,6 @@
|
||||||
|
#pragma once
|
||||||
|
#define PICOJSON_USE_INT64
|
||||||
|
-#include "picojson.h"
|
||||||
|
+#include "picojson/picojson.h"
|
||||||
|
#include "base.h"
|
||||||
|
#include <set>
|
||||||
|
#include <chrono>
|
|
@ -0,0 +1,31 @@
|
||||||
|
diff --git a/include/jwt-cpp/base.h b/include/jwt-cpp/base.h
|
||||||
|
index dfca7fc..4d05c0b 100644
|
||||||
|
--- a/include/jwt-cpp/base.h
|
||||||
|
+++ b/include/jwt-cpp/base.h
|
||||||
|
@@ -2,6 +2,10 @@
|
||||||
|
#include <string>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
+#ifdef _MSC_VER
|
||||||
|
+#pragma warning(disable : 4267)
|
||||||
|
+#endif
|
||||||
|
+
|
||||||
|
namespace jwt {
|
||||||
|
namespace alphabet {
|
||||||
|
struct base64 {
|
||||||
|
diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h
|
||||||
|
index ec56810..313cef2 100644
|
||||||
|
--- a/include/jwt-cpp/jwt.h
|
||||||
|
+++ b/include/jwt-cpp/jwt.h
|
||||||
|
@@ -12,6 +12,11 @@
|
||||||
|
#include <openssl/ec.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
|
||||||
|
+#ifdef _MSC_VER
|
||||||
|
+#pragma warning(disable : 4267)
|
||||||
|
+#pragma warning(disable : 4067)
|
||||||
|
+#endif
|
||||||
|
+
|
||||||
|
//If openssl version less than 1.1
|
||||||
|
#if OPENSSL_VERSION_NUMBER < 269484032
|
||||||
|
#define OPENSSL10
|
|
@ -0,0 +1,23 @@
|
||||||
|
#header-only library
|
||||||
|
include(vcpkg_common_functions)
|
||||||
|
|
||||||
|
set(SOURCE_PATH ${CURRENT_BUILDTREES_DIR}/src/jwt-cpp)
|
||||||
|
|
||||||
|
vcpkg_from_github(OUT_SOURCE_PATH SOURCE_PATH
|
||||||
|
REPO Thalhammer/jwt-cpp
|
||||||
|
REF f0e37a79f605312686065405dd720fc197cc3df0
|
||||||
|
SHA512 ae83c205dbb340dedc58d0d3f0e2453c4edcf5ce43b401f49d02692dc8a2a4b7260f1ced05ddfa7c1d5d6f92446e232629ddbdf67a58a119b50c5c8163591598
|
||||||
|
HEAD_REF master
|
||||||
|
PATCHES fix-picojson.patch
|
||||||
|
fix-warning.patch)
|
||||||
|
|
||||||
|
# Copy the constexpr header files
|
||||||
|
file(GLOB HEADER_FILES ${SOURCE_PATH}/include/jwt-cpp/*)
|
||||||
|
file(COPY ${HEADER_FILES}
|
||||||
|
DESTINATION ${CURRENT_PACKAGES_DIR}/include/jwt-cpp
|
||||||
|
REGEX "\.(gitattributes|gitignore|picojson.h)$" EXCLUDE)
|
||||||
|
|
||||||
|
# Put the licence file where vcpkg expects it
|
||||||
|
file(COPY ${SOURCE_PATH}/LICENSE
|
||||||
|
DESTINATION ${CURRENT_PACKAGES_DIR}/share/jwt-cpp)
|
||||||
|
file(RENAME ${CURRENT_PACKAGES_DIR}/share/jwt-cpp/LICENSE ${CURRENT_PACKAGES_DIR}/share/jwt-cpp/copyright)
|
|
@ -0,0 +1,90 @@
|
||||||
|
###################################################################################
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014, webvariants GmbH, http://www.webvariants.de
|
||||||
|
#
|
||||||
|
# This file is released under the terms of the MIT license. You can find the
|
||||||
|
# complete text in the attached LICENSE file or online at:
|
||||||
|
#
|
||||||
|
# http://www.opensource.org/licenses/mit-license.php
|
||||||
|
#
|
||||||
|
# @author: Tino Rusch (tino.rusch@webvariants.de)
|
||||||
|
#
|
||||||
|
###################################################################################
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
|
||||||
|
|
||||||
|
project(bcrypt)
|
||||||
|
|
||||||
|
enable_language(ASM)
|
||||||
|
|
||||||
|
set(MYLIB_VERSION_MAJOR 1)
|
||||||
|
set(MYLIB_VERSION_MINOR 0)
|
||||||
|
set(MYLIB_VERSION_PATCH 0)
|
||||||
|
set(MYLIB_VERSION_STRING ${MYLIB_VERSION_MAJOR}.${MYLIB_VERSION_MINOR}.${MYLIB_VERSION_PATCH})
|
||||||
|
|
||||||
|
# just doing cmake . will build a shared or static lib and honor existing environment setting
|
||||||
|
# to force build static, cmake . -DBUILD_SHARED_LIBS=Off
|
||||||
|
# to force build shared, cmake . -DBUILD_SHARED_LIBS=On
|
||||||
|
|
||||||
|
if (NOT BUILD_SHARED_LIBS)
|
||||||
|
message ("Building a static library")
|
||||||
|
else ()
|
||||||
|
message ("Building a shared library")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
set( CMAKE_COLOR_MAKEFILE ON )
|
||||||
|
|
||||||
|
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall --std=c++11 -O3" )
|
||||||
|
set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -O3" )
|
||||||
|
|
||||||
|
set( CMAKE_ASM_FLAGS "${CXXFLAGS} -x assembler-with-cpp")
|
||||||
|
|
||||||
|
set( SRCFILES
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/bcrypt.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/crypt_blowfish.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/crypt_gensalt.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/wrapper.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/x86.S
|
||||||
|
)
|
||||||
|
|
||||||
|
include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/include/bcrypt)
|
||||||
|
include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
|
||||||
|
add_library(
|
||||||
|
${PROJECT_NAME}
|
||||||
|
${SRCFILES}
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${MYLIB_VERSION_STRING} SOVERSION ${MYLIB_VERSION_MAJOR})
|
||||||
|
|
||||||
|
set_target_properties(${PROJECT_NAME} PROPERTIES PUBLIC_HEADER include/bcrypt/BCrypt.hpp)
|
||||||
|
|
||||||
|
target_include_directories(${PROJECT_NAME} PRIVATE include)
|
||||||
|
target_include_directories(${PROJECT_NAME} PRIVATE src)
|
||||||
|
|
||||||
|
add_executable( ${PROJECT_NAME}_test ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp)
|
||||||
|
|
||||||
|
target_link_libraries( ${PROJECT_NAME}_test ${PROJECT_NAME})
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
install(TARGETS ${PROJECT_NAME}
|
||||||
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/bcrypt)
|
||||||
|
|
||||||
|
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||||
|
FILES_MATCHING PATTERN "*.h")
|
||||||
|
|
||||||
|
SET(CPACK_GENERATOR "DEB")
|
||||||
|
SET(CPACK_SET_DESTDIR ON)
|
||||||
|
|
||||||
|
SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "Manuel Romei")
|
||||||
|
SET(CPACK_PACKAGE_VERSION "1.0.0")
|
||||||
|
SET(CPACK_PACKAGE_VERSION_MAJOR "1")
|
||||||
|
SET(CPACK_PACKAGE_VERSION_MINOR "0")
|
||||||
|
SET(CPACK_PACKAGE_VERSION_PATCH "0")
|
||||||
|
|
||||||
|
INCLUDE(CPack)
|
|
@ -0,0 +1,22 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 trusch
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
# libbcrypt
|
||||||
|
A c++ wrapper around bcrypt password hashing
|
||||||
|
|
||||||
|
## How to build this
|
||||||
|
This is a CMake based project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/trusch/libbcrypt
|
||||||
|
cd libbcrypt
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake ..
|
||||||
|
make
|
||||||
|
sudo make install
|
||||||
|
sudo ldconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to use this
|
||||||
|
|
||||||
|
Here an example how to use this wrapper class (you can find it in the src/ subdirectory)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "bcrypt/BCrypt.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
BCrypt bcrypt;
|
||||||
|
std::string password = "test";
|
||||||
|
std::string hash = bcrypt.generateHash(password);
|
||||||
|
std::cout<<bcrypt.validatePassword(password,hash)<<std::endl;
|
||||||
|
std::cout<<bcrypt.validatePassword("test1",hash)<<std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
build this with something like this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
g++ --std=c++11 -lbcrypt main.cpp
|
||||||
|
```
|
|
@ -0,0 +1,32 @@
|
||||||
|
#ifndef __BCRYPT__
|
||||||
|
#define __BCRYPT__
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include "winbcrypt.h"
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include "bcrypt.h"
|
||||||
|
#include <string>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
class BCrypt {
|
||||||
|
public:
|
||||||
|
static std::string generateHash(const std::string & password, int workload = 12){
|
||||||
|
char salt[BCRYPT_HASHSIZE];
|
||||||
|
char hash[BCRYPT_HASHSIZE];
|
||||||
|
int ret;
|
||||||
|
ret = bcrypt_gensalt(workload, salt);
|
||||||
|
if(ret != 0)throw std::runtime_error{"bcrypt: can not generate salt"};
|
||||||
|
ret = bcrypt_hashpw(password.c_str(), salt, hash);
|
||||||
|
if(ret != 0)throw std::runtime_error{"bcrypt: can not generate hash"};
|
||||||
|
return std::string{hash};
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool validatePassword(const std::string & password, const std::string & hash){
|
||||||
|
return (bcrypt_checkpw(password.c_str(), hash.c_str()) == 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // __BCRYPT__
|
|
@ -0,0 +1,97 @@
|
||||||
|
#ifndef BCRYPT_H_
|
||||||
|
#define BCRYPT_H_
|
||||||
|
/*
|
||||||
|
* bcrypt wrapper library
|
||||||
|
*
|
||||||
|
* Written in 2011, 2013, 2014, 2015 by Ricardo Garcia <r@rg3.name>
|
||||||
|
*
|
||||||
|
* To the extent possible under law, the author(s) have dedicated all copyright
|
||||||
|
* and related and neighboring rights to this software to the public domain
|
||||||
|
* worldwide. This software is distributed without any warranty.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
* with this software. If not, see
|
||||||
|
* <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define BCRYPT_HASHSIZE (64)
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function expects a work factor between 4 and 31 and a char array to
|
||||||
|
* store the resulting generated salt. The char array should typically have
|
||||||
|
* BCRYPT_HASHSIZE bytes at least. If the provided work factor is not in the
|
||||||
|
* previous range, it will default to 12.
|
||||||
|
*
|
||||||
|
* The return value is zero if the salt could be correctly generated and
|
||||||
|
* nonzero otherwise.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
int bcrypt_gensalt(int workfactor, char salt[BCRYPT_HASHSIZE]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function expects a password to be hashed, a salt to hash the password
|
||||||
|
* with and a char array to leave the result. Both the salt and the hash
|
||||||
|
* parameters should have room for BCRYPT_HASHSIZE characters at least.
|
||||||
|
*
|
||||||
|
* It can also be used to verify a hashed password. In that case, provide the
|
||||||
|
* expected hash in the salt parameter and verify the output hash is the same
|
||||||
|
* as the input hash. However, to avoid timing attacks, it's better to use
|
||||||
|
* bcrypt_checkpw when verifying a password.
|
||||||
|
*
|
||||||
|
* The return value is zero if the password could be hashed and nonzero
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
int bcrypt_hashpw(const char *passwd, const char salt[BCRYPT_HASHSIZE],
|
||||||
|
char hash[BCRYPT_HASHSIZE]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function expects a password and a hash to verify the password against.
|
||||||
|
* The internal implementation is tuned to avoid timing attacks.
|
||||||
|
*
|
||||||
|
* The return value will be -1 in case of errors, zero if the provided password
|
||||||
|
* matches the given hash and greater than zero if no errors are found and the
|
||||||
|
* passwords don't match.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
int bcrypt_checkpw(const char *passwd, const char hash[BCRYPT_HASHSIZE]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Brief Example
|
||||||
|
* -------------
|
||||||
|
*
|
||||||
|
* Hashing a password:
|
||||||
|
*
|
||||||
|
* char salt[BCRYPT_HASHSIZE];
|
||||||
|
* char hash[BCRYPT_HASHSIZE];
|
||||||
|
* int ret;
|
||||||
|
*
|
||||||
|
* ret = bcrypt_gensalt(12, salt);
|
||||||
|
* assert(ret == 0);
|
||||||
|
* ret = bcrypt_hashpw("thepassword", salt, hash);
|
||||||
|
* assert(ret == 0);
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Verifying a password:
|
||||||
|
*
|
||||||
|
* int ret;
|
||||||
|
*
|
||||||
|
* ret = bcrypt_checkpw("thepassword", "expectedhash");
|
||||||
|
* assert(ret != -1);
|
||||||
|
*
|
||||||
|
* if (ret == 0) {
|
||||||
|
* printf("The password matches\n");
|
||||||
|
* } else {
|
||||||
|
* printf("The password does NOT match\n");
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Written by Solar Designer <solar at openwall.com> in 2000-2002.
|
||||||
|
* No copyright is claimed, and the software is hereby placed in the public
|
||||||
|
* domain. In case this attempt to disclaim copyright and place the software
|
||||||
|
* in the public domain is deemed null and void, then the software is
|
||||||
|
* Copyright (c) 2000-2002 Solar Designer and it is hereby released to the
|
||||||
|
* general public under the following terms:
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted.
|
||||||
|
*
|
||||||
|
* There's ABSOLUTELY NO WARRANTY, express or implied.
|
||||||
|
*
|
||||||
|
* See crypt_blowfish.c for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gnu-crypt.h>
|
||||||
|
|
||||||
|
#if defined(_OW_SOURCE) || defined(__USE_OW)
|
||||||
|
#define __SKIP_GNU
|
||||||
|
#undef __SKIP_OW
|
||||||
|
#include <ow-crypt.h>
|
||||||
|
#undef __SKIP_GNU
|
||||||
|
#endif
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Written by Solar Designer <solar at openwall.com> in 2000-2011.
|
||||||
|
* No copyright is claimed, and the software is hereby placed in the public
|
||||||
|
* domain. In case this attempt to disclaim copyright and place the software
|
||||||
|
* in the public domain is deemed null and void, then the software is
|
||||||
|
* Copyright (c) 2000-2011 Solar Designer and it is hereby released to the
|
||||||
|
* general public under the following terms:
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted.
|
||||||
|
*
|
||||||
|
* There's ABSOLUTELY NO WARRANTY, express or implied.
|
||||||
|
*
|
||||||
|
* See crypt_blowfish.c for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _CRYPT_BLOWFISH_H
|
||||||
|
#define _CRYPT_BLOWFISH_H
|
||||||
|
|
||||||
|
extern int _crypt_output_magic(const char *setting, char *output, int size);
|
||||||
|
extern char *_crypt_blowfish_rn(const char *key, const char *setting,
|
||||||
|
char *output, int size);
|
||||||
|
extern char *_crypt_gensalt_blowfish_rn(const char *prefix,
|
||||||
|
unsigned long count,
|
||||||
|
const char *input, int size, char *output, int output_size);
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Written by Solar Designer <solar at openwall.com> in 2000-2011.
|
||||||
|
* No copyright is claimed, and the software is hereby placed in the public
|
||||||
|
* domain. In case this attempt to disclaim copyright and place the software
|
||||||
|
* in the public domain is deemed null and void, then the software is
|
||||||
|
* Copyright (c) 2000-2011 Solar Designer and it is hereby released to the
|
||||||
|
* general public under the following terms:
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted.
|
||||||
|
*
|
||||||
|
* There's ABSOLUTELY NO WARRANTY, express or implied.
|
||||||
|
*
|
||||||
|
* See crypt_blowfish.c for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _CRYPT_GENSALT_H
|
||||||
|
#define _CRYPT_GENSALT_H
|
||||||
|
|
||||||
|
extern unsigned char _crypt_itoa64[];
|
||||||
|
extern char *_crypt_gensalt_traditional_rn(const char *prefix,
|
||||||
|
unsigned long count,
|
||||||
|
const char *input, int size, char *output, int output_size);
|
||||||
|
extern char *_crypt_gensalt_extended_rn(const char *prefix,
|
||||||
|
unsigned long count,
|
||||||
|
const char *input, int size, char *output, int output_size);
|
||||||
|
extern char *_crypt_gensalt_md5_rn(const char *prefix, unsigned long count,
|
||||||
|
const char *input, int size, char *output, int output_size);
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Written by Solar Designer <solar at openwall.com> in 2000-2011.
|
||||||
|
* No copyright is claimed, and the software is hereby placed in the public
|
||||||
|
* domain. In case this attempt to disclaim copyright and place the software
|
||||||
|
* in the public domain is deemed null and void, then the software is
|
||||||
|
* Copyright (c) 2000-2011 Solar Designer and it is hereby released to the
|
||||||
|
* general public under the following terms:
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted.
|
||||||
|
*
|
||||||
|
* There's ABSOLUTELY NO WARRANTY, express or implied.
|
||||||
|
*
|
||||||
|
* See crypt_blowfish.c for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _OW_CRYPT_H
|
||||||
|
#define _OW_CRYPT_H
|
||||||
|
|
||||||
|
#ifndef __GNUC__
|
||||||
|
#undef __const
|
||||||
|
#define __const const
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef __SKIP_GNU
|
||||||
|
extern char *crypt(__const char *key, __const char *setting);
|
||||||
|
extern char *crypt_r(__const char *key, __const char *setting, void *data);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef __SKIP_OW
|
||||||
|
extern char *crypt_rn(__const char *key, __const char *setting,
|
||||||
|
void *data, int size);
|
||||||
|
extern char *crypt_ra(__const char *key, __const char *setting,
|
||||||
|
void **data, int *size);
|
||||||
|
extern char *crypt_gensalt(__const char *prefix, unsigned long count,
|
||||||
|
__const char *input, int size);
|
||||||
|
extern char *crypt_gensalt_rn(__const char *prefix, unsigned long count,
|
||||||
|
__const char *input, int size, char *output, int output_size);
|
||||||
|
extern char *crypt_gensalt_ra(__const char *prefix, unsigned long count,
|
||||||
|
__const char *input, int size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,27 @@
|
||||||
|
#ifndef __WIN_BCRYPT__H
|
||||||
|
#define __WIN_BCRYPT__H
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "crypt_blowfish.h"
|
||||||
|
#include "./bcrypt.h"
|
||||||
|
|
||||||
|
class BCrypt {
|
||||||
|
public:
|
||||||
|
static std::string generateHash(const std::string & password, int workload = 12) {
|
||||||
|
char salt[BCRYPT_HASHSIZE];
|
||||||
|
char hash[BCRYPT_HASHSIZE];
|
||||||
|
int ret;
|
||||||
|
ret = bcrypt_gensalt(workload, salt);
|
||||||
|
if (ret != 0)throw std::runtime_error{ "bcrypt: can not generate salt" };
|
||||||
|
ret = bcrypt_hashpw(password.c_str(), salt, hash);
|
||||||
|
if (ret != 0)throw std::runtime_error{ "bcrypt: can not generate hash" };
|
||||||
|
return std::string{ hash };
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool validatePassword(const std::string & password, const std::string & hash) {
|
||||||
|
return (bcrypt_checkpw(password.c_str(), hash.c_str()) == 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,230 @@
|
||||||
|
/*
|
||||||
|
* bcrypt wrapper library
|
||||||
|
*
|
||||||
|
* Written in 2011, 2013, 2014, 2015 by Ricardo Garcia <r@rg3.name>
|
||||||
|
*
|
||||||
|
* To the extent possible under law, the author(s) have dedicated all copyright
|
||||||
|
* and related and neighboring rights to this software to the public domain
|
||||||
|
* worldwide. This software is distributed without any warranty.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
* with this software. If not, see
|
||||||
|
* <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
*/
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#ifdef _WIN32
|
||||||
|
#elif _WIN64
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32 || _WIN64
|
||||||
|
// On windows we need to generate random bytes differently.
|
||||||
|
typedef __int64 ssize_t;
|
||||||
|
#define BCRYPT_HASHSIZE 60
|
||||||
|
|
||||||
|
#include "../include/bcrypt/bcrypt.h"
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <wincrypt.h> /* CryptAcquireContext, CryptGenRandom */
|
||||||
|
#else
|
||||||
|
#include "bcrypt.h"
|
||||||
|
#include "ow-crypt.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define RANDBYTES (16)
|
||||||
|
|
||||||
|
static int try_close(int fd)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
for (;;) {
|
||||||
|
errno = 0;
|
||||||
|
ret = close(fd);
|
||||||
|
if (ret == -1 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int try_read(int fd, char *out, size_t count)
|
||||||
|
{
|
||||||
|
size_t total;
|
||||||
|
ssize_t partial;
|
||||||
|
|
||||||
|
total = 0;
|
||||||
|
while (total < count)
|
||||||
|
{
|
||||||
|
for (;;) {
|
||||||
|
errno = 0;
|
||||||
|
partial = read(fd, out + total, count - total);
|
||||||
|
if (partial == -1 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partial < 1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
total += partial;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is a best effort implementation. Nothing prevents a compiler from
|
||||||
|
* optimizing this function and making it vulnerable to timing attacks, but
|
||||||
|
* this method is commonly used in crypto libraries like NaCl.
|
||||||
|
*
|
||||||
|
* Return value is zero if both strings are equal and nonzero otherwise.
|
||||||
|
*/
|
||||||
|
static int timing_safe_strcmp(const char *str1, const char *str2)
|
||||||
|
{
|
||||||
|
const unsigned char *u1;
|
||||||
|
const unsigned char *u2;
|
||||||
|
int ret;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
int len1 = strlen(str1);
|
||||||
|
int len2 = strlen(str2);
|
||||||
|
|
||||||
|
/* In our context both strings should always have the same length
|
||||||
|
* because they will be hashed passwords. */
|
||||||
|
if (len1 != len2)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
/* Force unsigned for bitwise operations. */
|
||||||
|
u1 = (const unsigned char *)str1;
|
||||||
|
u2 = (const unsigned char *)str2;
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
for (i = 0; i < len1; ++i)
|
||||||
|
ret |= (u1[i] ^ u2[i]);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bcrypt_gensalt(int factor, char salt[BCRYPT_HASHSIZE])
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
char input[RANDBYTES];
|
||||||
|
int workf;
|
||||||
|
char *aux;
|
||||||
|
|
||||||
|
// Note: Windows does not have /dev/urandom sadly.
|
||||||
|
#ifdef _WIN32 || _WIN64
|
||||||
|
HCRYPTPROV p;
|
||||||
|
ULONG i;
|
||||||
|
|
||||||
|
// Acquire a crypt context for generating random bytes.
|
||||||
|
if (CryptAcquireContext(&p, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) == FALSE) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CryptGenRandom(p, RANDBYTES, (BYTE*)input) == FALSE) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CryptReleaseContext(p, 0) == FALSE) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// Get random bytes on Unix/Linux.
|
||||||
|
fd = open("/dev/urandom", O_RDONLY);
|
||||||
|
if (fd == -1)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (try_read(fd, input, RANDBYTES) != 0) {
|
||||||
|
if (try_close(fd) != 0)
|
||||||
|
return 4;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (try_close(fd) != 0)
|
||||||
|
return 3;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Generate salt. */
|
||||||
|
workf = (factor < 4 || factor > 31)?12:factor;
|
||||||
|
aux = crypt_gensalt_rn("$2a$", workf, input, RANDBYTES,
|
||||||
|
salt, BCRYPT_HASHSIZE);
|
||||||
|
return (aux == NULL)?5:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bcrypt_hashpw(const char *passwd, const char salt[BCRYPT_HASHSIZE], char hash[BCRYPT_HASHSIZE])
|
||||||
|
{
|
||||||
|
char *aux;
|
||||||
|
aux = crypt_rn(passwd, salt, hash, BCRYPT_HASHSIZE);
|
||||||
|
return (aux == NULL)?1:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bcrypt_checkpw(const char *passwd, const char hash[BCRYPT_HASHSIZE])
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
char outhash[BCRYPT_HASHSIZE];
|
||||||
|
|
||||||
|
ret = bcrypt_hashpw(passwd, hash, outhash);
|
||||||
|
if (ret != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return timing_safe_strcmp(hash, outhash);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef TEST_BCRYPT
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
clock_t before;
|
||||||
|
clock_t after;
|
||||||
|
char salt[BCRYPT_HASHSIZE];
|
||||||
|
char hash[BCRYPT_HASHSIZE];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
const char pass[] = "hi,mom";
|
||||||
|
const char hash1[] = "$2a$10$VEVmGHy4F4XQMJ3eOZJAUeb.MedU0W10pTPCuf53eHdKJPiSE8sMK";
|
||||||
|
const char hash2[] = "$2a$10$3F0BVk5t8/aoS.3ddaB3l.fxg5qvafQ9NybxcpXLzMeAt.nVWn.NO";
|
||||||
|
|
||||||
|
ret = bcrypt_gensalt(12, salt);
|
||||||
|
assert(ret == 0);
|
||||||
|
printf("Generated salt: %s\n", salt);
|
||||||
|
before = clock();
|
||||||
|
ret = bcrypt_hashpw("testtesttest", salt, hash);
|
||||||
|
assert(ret == 0);
|
||||||
|
after = clock();
|
||||||
|
printf("Hashed password: %s\n", hash);
|
||||||
|
printf("Time taken: %f seconds\n",
|
||||||
|
(double)(after - before) / CLOCKS_PER_SEC);
|
||||||
|
|
||||||
|
ret = bcrypt_hashpw(pass, hash1, hash);
|
||||||
|
assert(ret == 0);
|
||||||
|
printf("First hash check: %s\n", (strcmp(hash1, hash) == 0)?"OK":"FAIL");
|
||||||
|
ret = bcrypt_hashpw(pass, hash2, hash);
|
||||||
|
assert(ret == 0);
|
||||||
|
printf("Second hash check: %s\n", (strcmp(hash2, hash) == 0)?"OK":"FAIL");
|
||||||
|
|
||||||
|
before = clock();
|
||||||
|
ret = (bcrypt_checkpw(pass, hash1) == 0);
|
||||||
|
after = clock();
|
||||||
|
printf("First hash check with bcrypt_checkpw: %s\n", ret?"OK":"FAIL");
|
||||||
|
printf("Time taken: %f seconds\n",
|
||||||
|
(double)(after - before) / CLOCKS_PER_SEC);
|
||||||
|
|
||||||
|
before = clock();
|
||||||
|
ret = (bcrypt_checkpw(pass, hash2) == 0);
|
||||||
|
after = clock();
|
||||||
|
printf("Second hash check with bcrypt_checkpw: %s\n", ret?"OK":"FAIL");
|
||||||
|
printf("Time taken: %f seconds\n",
|
||||||
|
(double)(after - before) / CLOCKS_PER_SEC);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,911 @@
|
||||||
|
/*
|
||||||
|
* The crypt_blowfish homepage is:
|
||||||
|
*
|
||||||
|
* http://www.openwall.com/crypt/
|
||||||
|
*
|
||||||
|
* This code comes from John the Ripper password cracker, with reentrant
|
||||||
|
* and crypt(3) interfaces added, but optimizations specific to password
|
||||||
|
* cracking removed.
|
||||||
|
*
|
||||||
|
* Written by Solar Designer <solar at openwall.com> in 1998-2014.
|
||||||
|
* No copyright is claimed, and the software is hereby placed in the public
|
||||||
|
* domain. In case this attempt to disclaim copyright and place the software
|
||||||
|
* in the public domain is deemed null and void, then the software is
|
||||||
|
* Copyright (c) 1998-2014 Solar Designer and it is hereby released to the
|
||||||
|
* general public under the following terms:
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted.
|
||||||
|
*
|
||||||
|
* There's ABSOLUTELY NO WARRANTY, express or implied.
|
||||||
|
*
|
||||||
|
* It is my intent that you should be able to use this on your system,
|
||||||
|
* as part of a software package, or anywhere else to improve security,
|
||||||
|
* ensure compatibility, or for any other purpose. I would appreciate
|
||||||
|
* it if you give credit where it is due and keep your modifications in
|
||||||
|
* the public domain as well, but I don't require that in order to let
|
||||||
|
* you place this code and any modifications you make under a license
|
||||||
|
* of your choice.
|
||||||
|
*
|
||||||
|
* This implementation is fully compatible with OpenBSD's bcrypt.c for prefix
|
||||||
|
* "$2b$", originally by Niels Provos <provos at citi.umich.edu>, and it uses
|
||||||
|
* some of his ideas. The password hashing algorithm was designed by David
|
||||||
|
* Mazieres <dm at lcs.mit.edu>. For information on the level of
|
||||||
|
* compatibility for bcrypt hash prefixes other than "$2b$", please refer to
|
||||||
|
* the comments in BF_set_key() below and to the included crypt(3) man page.
|
||||||
|
*
|
||||||
|
* There's a paper on the algorithm that explains its design decisions:
|
||||||
|
*
|
||||||
|
* http://www.usenix.org/events/usenix99/provos.html
|
||||||
|
*
|
||||||
|
* Some of the tricks in BF_ROUND might be inspired by Eric Young's
|
||||||
|
* Blowfish library (I can't be sure if I would think of something if I
|
||||||
|
* hadn't seen his code).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#ifndef __set_errno
|
||||||
|
#define __set_errno(val) errno = (val)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Just to make sure the prototypes match the actual definitions */
|
||||||
|
#ifdef _WIN32 || _WIN64
|
||||||
|
#include "../include/bcrypt/crypt_blowfish.h"
|
||||||
|
#else
|
||||||
|
#include "crypt_blowfish.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __i386__
|
||||||
|
#define BF_ASM 1
|
||||||
|
#define BF_SCALE 1
|
||||||
|
#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__)
|
||||||
|
#define BF_ASM 0
|
||||||
|
#define BF_SCALE 1
|
||||||
|
#else
|
||||||
|
#define BF_ASM 0
|
||||||
|
#define BF_SCALE 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef unsigned int BF_word;
|
||||||
|
typedef signed int BF_word_signed;
|
||||||
|
|
||||||
|
/* Number of Blowfish rounds, this is also hardcoded into a few places */
|
||||||
|
#define BF_N 16
|
||||||
|
|
||||||
|
typedef BF_word BF_key[BF_N + 2];
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
BF_word S[4][0x100];
|
||||||
|
BF_key P;
|
||||||
|
} BF_ctx;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Magic IV for 64 Blowfish encryptions that we do at the end.
|
||||||
|
* The string is "OrpheanBeholderScryDoubt" on big-endian.
|
||||||
|
*/
|
||||||
|
static BF_word BF_magic_w[6] = {
|
||||||
|
0x4F727068, 0x65616E42, 0x65686F6C,
|
||||||
|
0x64657253, 0x63727944, 0x6F756274
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* P-box and S-box tables initialized with digits of Pi.
|
||||||
|
*/
|
||||||
|
static BF_ctx BF_init_state = {
|
||||||
|
{
|
||||||
|
{
|
||||||
|
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
|
||||||
|
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
|
||||||
|
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
|
||||||
|
0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
|
||||||
|
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
|
||||||
|
0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
|
||||||
|
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
|
||||||
|
0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
|
||||||
|
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
|
||||||
|
0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
|
||||||
|
0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
|
||||||
|
0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
|
||||||
|
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
|
||||||
|
0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
|
||||||
|
0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
|
||||||
|
0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
|
||||||
|
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
|
||||||
|
0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
|
||||||
|
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
|
||||||
|
0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
|
||||||
|
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
|
||||||
|
0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
|
||||||
|
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
|
||||||
|
0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
|
||||||
|
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
|
||||||
|
0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
|
||||||
|
0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
|
||||||
|
0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
|
||||||
|
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
|
||||||
|
0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
|
||||||
|
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
|
||||||
|
0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
|
||||||
|
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
|
||||||
|
0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
|
||||||
|
0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
|
||||||
|
0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
|
||||||
|
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
|
||||||
|
0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
|
||||||
|
0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
|
||||||
|
0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
|
||||||
|
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
|
||||||
|
0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
|
||||||
|
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
|
||||||
|
0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
|
||||||
|
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
|
||||||
|
0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
|
||||||
|
0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
|
||||||
|
0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
|
||||||
|
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
|
||||||
|
0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
|
||||||
|
0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
|
||||||
|
0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
|
||||||
|
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
|
||||||
|
0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
|
||||||
|
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
|
||||||
|
0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
|
||||||
|
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
|
||||||
|
0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
|
||||||
|
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
|
||||||
|
0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
|
||||||
|
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
|
||||||
|
0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
|
||||||
|
0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
|
||||||
|
0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a
|
||||||
|
}, {
|
||||||
|
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
|
||||||
|
0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
|
||||||
|
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
|
||||||
|
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
|
||||||
|
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
|
||||||
|
0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
|
||||||
|
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
|
||||||
|
0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
|
||||||
|
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
|
||||||
|
0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
|
||||||
|
0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
|
||||||
|
0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
|
||||||
|
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
|
||||||
|
0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
|
||||||
|
0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
|
||||||
|
0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
|
||||||
|
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
|
||||||
|
0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
|
||||||
|
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
|
||||||
|
0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
|
||||||
|
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
|
||||||
|
0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
|
||||||
|
0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
|
||||||
|
0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
|
||||||
|
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
|
||||||
|
0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
|
||||||
|
0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
|
||||||
|
0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
|
||||||
|
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
|
||||||
|
0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
|
||||||
|
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
|
||||||
|
0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
|
||||||
|
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
|
||||||
|
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
|
||||||
|
0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
|
||||||
|
0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
|
||||||
|
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
|
||||||
|
0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
|
||||||
|
0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
|
||||||
|
0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
|
||||||
|
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
|
||||||
|
0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
|
||||||
|
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
|
||||||
|
0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
|
||||||
|
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
|
||||||
|
0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
|
||||||
|
0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
|
||||||
|
0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
|
||||||
|
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
|
||||||
|
0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
|
||||||
|
0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
|
||||||
|
0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
|
||||||
|
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
|
||||||
|
0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
|
||||||
|
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
|
||||||
|
0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
|
||||||
|
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
|
||||||
|
0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
|
||||||
|
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
|
||||||
|
0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
|
||||||
|
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
|
||||||
|
0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
|
||||||
|
0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
|
||||||
|
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7
|
||||||
|
}, {
|
||||||
|
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
|
||||||
|
0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
|
||||||
|
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
|
||||||
|
0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
|
||||||
|
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
|
||||||
|
0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
|
||||||
|
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
|
||||||
|
0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
|
||||||
|
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
|
||||||
|
0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
|
||||||
|
0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
|
||||||
|
0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
|
||||||
|
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
|
||||||
|
0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
|
||||||
|
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
|
||||||
|
0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
|
||||||
|
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
|
||||||
|
0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
|
||||||
|
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
|
||||||
|
0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
|
||||||
|
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
|
||||||
|
0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
|
||||||
|
0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
|
||||||
|
0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
|
||||||
|
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
|
||||||
|
0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
|
||||||
|
0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
|
||||||
|
0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
|
||||||
|
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
|
||||||
|
0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
|
||||||
|
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
|
||||||
|
0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
|
||||||
|
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
|
||||||
|
0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
|
||||||
|
0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
|
||||||
|
0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
|
||||||
|
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
|
||||||
|
0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
|
||||||
|
0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
|
||||||
|
0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
|
||||||
|
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
|
||||||
|
0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
|
||||||
|
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
|
||||||
|
0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
|
||||||
|
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
|
||||||
|
0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
|
||||||
|
0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
|
||||||
|
0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
|
||||||
|
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
|
||||||
|
0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
|
||||||
|
0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
|
||||||
|
0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
|
||||||
|
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
|
||||||
|
0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
|
||||||
|
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
|
||||||
|
0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
|
||||||
|
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
|
||||||
|
0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
|
||||||
|
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
|
||||||
|
0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
|
||||||
|
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
|
||||||
|
0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
|
||||||
|
0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
|
||||||
|
0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0
|
||||||
|
}, {
|
||||||
|
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
|
||||||
|
0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
|
||||||
|
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
|
||||||
|
0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
|
||||||
|
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
|
||||||
|
0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
|
||||||
|
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
|
||||||
|
0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
|
||||||
|
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
|
||||||
|
0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
|
||||||
|
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
|
||||||
|
0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
|
||||||
|
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
|
||||||
|
0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
|
||||||
|
0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
|
||||||
|
0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
|
||||||
|
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
|
||||||
|
0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
|
||||||
|
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
|
||||||
|
0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
|
||||||
|
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
|
||||||
|
0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
|
||||||
|
0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
|
||||||
|
0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
|
||||||
|
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
|
||||||
|
0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
|
||||||
|
0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
|
||||||
|
0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
|
||||||
|
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
|
||||||
|
0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
|
||||||
|
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
|
||||||
|
0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
|
||||||
|
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
|
||||||
|
0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
|
||||||
|
0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
|
||||||
|
0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
|
||||||
|
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
|
||||||
|
0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
|
||||||
|
0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
|
||||||
|
0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
|
||||||
|
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
|
||||||
|
0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
|
||||||
|
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
|
||||||
|
0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
|
||||||
|
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
|
||||||
|
0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
|
||||||
|
0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
|
||||||
|
0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
|
||||||
|
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
|
||||||
|
0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
|
||||||
|
0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
|
||||||
|
0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
|
||||||
|
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
|
||||||
|
0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
|
||||||
|
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
|
||||||
|
0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
|
||||||
|
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
|
||||||
|
0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
|
||||||
|
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
|
||||||
|
0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
|
||||||
|
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
|
||||||
|
0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
|
||||||
|
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
|
||||||
|
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
|
||||||
|
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
|
||||||
|
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
|
||||||
|
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
|
||||||
|
0x9216d5d9, 0x8979fb1b
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static unsigned char BF_itoa64[64 + 1] =
|
||||||
|
"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
|
||||||
|
static unsigned char BF_atoi64[0x60] = {
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1,
|
||||||
|
54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||||
|
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 64, 64, 64, 64, 64,
|
||||||
|
64, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
|
||||||
|
43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 64, 64, 64, 64, 64
|
||||||
|
};
|
||||||
|
|
||||||
|
#define BF_safe_atoi64(dst, src) \
|
||||||
|
{ \
|
||||||
|
tmp = (unsigned char)(src); \
|
||||||
|
if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \
|
||||||
|
tmp = BF_atoi64[tmp]; \
|
||||||
|
if (tmp > 63) return -1; \
|
||||||
|
(dst) = tmp; \
|
||||||
|
}
|
||||||
|
|
||||||
|
static int BF_decode(BF_word *dst, const char *src, int size)
|
||||||
|
{
|
||||||
|
unsigned char *dptr = (unsigned char *)dst;
|
||||||
|
unsigned char *end = dptr + size;
|
||||||
|
const unsigned char *sptr = (const unsigned char *)src;
|
||||||
|
unsigned int tmp, c1, c2, c3, c4;
|
||||||
|
|
||||||
|
do {
|
||||||
|
BF_safe_atoi64(c1, *sptr++);
|
||||||
|
BF_safe_atoi64(c2, *sptr++);
|
||||||
|
*dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4);
|
||||||
|
if (dptr >= end) break;
|
||||||
|
|
||||||
|
BF_safe_atoi64(c3, *sptr++);
|
||||||
|
*dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2);
|
||||||
|
if (dptr >= end) break;
|
||||||
|
|
||||||
|
BF_safe_atoi64(c4, *sptr++);
|
||||||
|
*dptr++ = ((c3 & 0x03) << 6) | c4;
|
||||||
|
} while (dptr < end);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BF_encode(char *dst, const BF_word *src, int size)
|
||||||
|
{
|
||||||
|
const unsigned char *sptr = (const unsigned char *)src;
|
||||||
|
const unsigned char *end = sptr + size;
|
||||||
|
unsigned char *dptr = (unsigned char *)dst;
|
||||||
|
unsigned int c1, c2;
|
||||||
|
|
||||||
|
do {
|
||||||
|
c1 = *sptr++;
|
||||||
|
*dptr++ = BF_itoa64[c1 >> 2];
|
||||||
|
c1 = (c1 & 0x03) << 4;
|
||||||
|
if (sptr >= end) {
|
||||||
|
*dptr++ = BF_itoa64[c1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
c2 = *sptr++;
|
||||||
|
c1 |= c2 >> 4;
|
||||||
|
*dptr++ = BF_itoa64[c1];
|
||||||
|
c1 = (c2 & 0x0f) << 2;
|
||||||
|
if (sptr >= end) {
|
||||||
|
*dptr++ = BF_itoa64[c1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
c2 = *sptr++;
|
||||||
|
c1 |= c2 >> 6;
|
||||||
|
*dptr++ = BF_itoa64[c1];
|
||||||
|
*dptr++ = BF_itoa64[c2 & 0x3f];
|
||||||
|
} while (sptr < end);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BF_swap(BF_word *x, int count)
|
||||||
|
{
|
||||||
|
static int endianness_check = 1;
|
||||||
|
char *is_little_endian = (char *)&endianness_check;
|
||||||
|
BF_word tmp;
|
||||||
|
|
||||||
|
if (*is_little_endian)
|
||||||
|
do {
|
||||||
|
tmp = *x;
|
||||||
|
tmp = (tmp << 16) | (tmp >> 16);
|
||||||
|
*x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF);
|
||||||
|
} while (--count);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if BF_SCALE
|
||||||
|
/* Architectures which can shift addresses left by 2 bits with no extra cost */
|
||||||
|
#define BF_ROUND(L, R, N) \
|
||||||
|
tmp1 = L & 0xFF; \
|
||||||
|
tmp2 = L >> 8; \
|
||||||
|
tmp2 &= 0xFF; \
|
||||||
|
tmp3 = L >> 16; \
|
||||||
|
tmp3 &= 0xFF; \
|
||||||
|
tmp4 = L >> 24; \
|
||||||
|
tmp1 = data.ctx.S[3][tmp1]; \
|
||||||
|
tmp2 = data.ctx.S[2][tmp2]; \
|
||||||
|
tmp3 = data.ctx.S[1][tmp3]; \
|
||||||
|
tmp3 += data.ctx.S[0][tmp4]; \
|
||||||
|
tmp3 ^= tmp2; \
|
||||||
|
R ^= data.ctx.P[N + 1]; \
|
||||||
|
tmp3 += tmp1; \
|
||||||
|
R ^= tmp3;
|
||||||
|
#else
|
||||||
|
/* Architectures with no complicated addressing modes supported */
|
||||||
|
#define BF_INDEX(S, i) \
|
||||||
|
(*((BF_word *)(((unsigned char *)S) + (i))))
|
||||||
|
#define BF_ROUND(L, R, N) \
|
||||||
|
tmp1 = L & 0xFF; \
|
||||||
|
tmp1 <<= 2; \
|
||||||
|
tmp2 = L >> 6; \
|
||||||
|
tmp2 &= 0x3FC; \
|
||||||
|
tmp3 = L >> 14; \
|
||||||
|
tmp3 &= 0x3FC; \
|
||||||
|
tmp4 = L >> 22; \
|
||||||
|
tmp4 &= 0x3FC; \
|
||||||
|
tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \
|
||||||
|
tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \
|
||||||
|
tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \
|
||||||
|
tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \
|
||||||
|
tmp3 ^= tmp2; \
|
||||||
|
R ^= data.ctx.P[N + 1]; \
|
||||||
|
tmp3 += tmp1; \
|
||||||
|
R ^= tmp3;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Encrypt one block, BF_N is hardcoded here.
|
||||||
|
*/
|
||||||
|
#define BF_ENCRYPT \
|
||||||
|
L ^= data.ctx.P[0]; \
|
||||||
|
BF_ROUND(L, R, 0); \
|
||||||
|
BF_ROUND(R, L, 1); \
|
||||||
|
BF_ROUND(L, R, 2); \
|
||||||
|
BF_ROUND(R, L, 3); \
|
||||||
|
BF_ROUND(L, R, 4); \
|
||||||
|
BF_ROUND(R, L, 5); \
|
||||||
|
BF_ROUND(L, R, 6); \
|
||||||
|
BF_ROUND(R, L, 7); \
|
||||||
|
BF_ROUND(L, R, 8); \
|
||||||
|
BF_ROUND(R, L, 9); \
|
||||||
|
BF_ROUND(L, R, 10); \
|
||||||
|
BF_ROUND(R, L, 11); \
|
||||||
|
BF_ROUND(L, R, 12); \
|
||||||
|
BF_ROUND(R, L, 13); \
|
||||||
|
BF_ROUND(L, R, 14); \
|
||||||
|
BF_ROUND(R, L, 15); \
|
||||||
|
tmp4 = R; \
|
||||||
|
R = L; \
|
||||||
|
L = tmp4 ^ data.ctx.P[BF_N + 1];
|
||||||
|
|
||||||
|
#if BF_ASM
|
||||||
|
#define BF_body() \
|
||||||
|
_BF_body_r(&data.ctx);
|
||||||
|
#else
|
||||||
|
#define BF_body() \
|
||||||
|
L = R = 0; \
|
||||||
|
ptr = data.ctx.P; \
|
||||||
|
do { \
|
||||||
|
ptr += 2; \
|
||||||
|
BF_ENCRYPT; \
|
||||||
|
*(ptr - 2) = L; \
|
||||||
|
*(ptr - 1) = R; \
|
||||||
|
} while (ptr < &data.ctx.P[BF_N + 2]); \
|
||||||
|
\
|
||||||
|
ptr = data.ctx.S[0]; \
|
||||||
|
do { \
|
||||||
|
ptr += 2; \
|
||||||
|
BF_ENCRYPT; \
|
||||||
|
*(ptr - 2) = L; \
|
||||||
|
*(ptr - 1) = R; \
|
||||||
|
} while (ptr < &data.ctx.S[3][0xFF]);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void BF_set_key(const char *key, BF_key expanded, BF_key initial,
|
||||||
|
unsigned char flags)
|
||||||
|
{
|
||||||
|
const char *ptr = key;
|
||||||
|
unsigned int bug, i, j;
|
||||||
|
BF_word safety, sign, diff, tmp[2];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* There was a sign extension bug in older revisions of this function. While
|
||||||
|
* we would have liked to simply fix the bug and move on, we have to provide
|
||||||
|
* a backwards compatibility feature (essentially the bug) for some systems and
|
||||||
|
* a safety measure for some others. The latter is needed because for certain
|
||||||
|
* multiple inputs to the buggy algorithm there exist easily found inputs to
|
||||||
|
* the correct algorithm that produce the same hash. Thus, we optionally
|
||||||
|
* deviate from the correct algorithm just enough to avoid such collisions.
|
||||||
|
* While the bug itself affected the majority of passwords containing
|
||||||
|
* characters with the 8th bit set (although only a percentage of those in a
|
||||||
|
* collision-producing way), the anti-collision safety measure affects
|
||||||
|
* only a subset of passwords containing the '\xff' character (not even all of
|
||||||
|
* those passwords, just some of them). This character is not found in valid
|
||||||
|
* UTF-8 sequences and is rarely used in popular 8-bit character encodings.
|
||||||
|
* Thus, the safety measure is unlikely to cause much annoyance, and is a
|
||||||
|
* reasonable tradeoff to use when authenticating against existing hashes that
|
||||||
|
* are not reliably known to have been computed with the correct algorithm.
|
||||||
|
*
|
||||||
|
* We use an approach that tries to minimize side-channel leaks of password
|
||||||
|
* information - that is, we mostly use fixed-cost bitwise operations instead
|
||||||
|
* of branches or table lookups. (One conditional branch based on password
|
||||||
|
* length remains. It is not part of the bug aftermath, though, and is
|
||||||
|
* difficult and possibly unreasonable to avoid given the use of C strings by
|
||||||
|
* the caller, which results in similar timing leaks anyway.)
|
||||||
|
*
|
||||||
|
* For actual implementation, we set an array index in the variable "bug"
|
||||||
|
* (0 means no bug, 1 means sign extension bug emulation) and a flag in the
|
||||||
|
* variable "safety" (bit 16 is set when the safety measure is requested).
|
||||||
|
* Valid combinations of settings are:
|
||||||
|
*
|
||||||
|
* Prefix "$2a$": bug = 0, safety = 0x10000
|
||||||
|
* Prefix "$2b$": bug = 0, safety = 0
|
||||||
|
* Prefix "$2x$": bug = 1, safety = 0
|
||||||
|
* Prefix "$2y$": bug = 0, safety = 0
|
||||||
|
*/
|
||||||
|
bug = (unsigned int)flags & 1;
|
||||||
|
safety = ((BF_word)flags & 2) << 15;
|
||||||
|
|
||||||
|
sign = diff = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < BF_N + 2; i++) {
|
||||||
|
tmp[0] = tmp[1] = 0;
|
||||||
|
for (j = 0; j < 4; j++) {
|
||||||
|
tmp[0] <<= 8;
|
||||||
|
tmp[0] |= (unsigned char)*ptr; /* correct */
|
||||||
|
tmp[1] <<= 8;
|
||||||
|
tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */
|
||||||
|
/*
|
||||||
|
* Sign extension in the first char has no effect - nothing to overwrite yet,
|
||||||
|
* and those extra 24 bits will be fully shifted out of the 32-bit word. For
|
||||||
|
* chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign
|
||||||
|
* extension in tmp[1] occurs. Once this flag is set, it remains set.
|
||||||
|
*/
|
||||||
|
if (j)
|
||||||
|
sign |= tmp[1] & 0x80;
|
||||||
|
if (!*ptr)
|
||||||
|
ptr = key;
|
||||||
|
else
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */
|
||||||
|
|
||||||
|
expanded[i] = tmp[bug];
|
||||||
|
initial[i] = BF_init_state.P[i] ^ tmp[bug];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* At this point, "diff" is zero iff the correct and buggy algorithms produced
|
||||||
|
* exactly the same result. If so and if "sign" is non-zero, which indicates
|
||||||
|
* that there was a non-benign sign extension, this means that we have a
|
||||||
|
* collision between the correctly computed hash for this password and a set of
|
||||||
|
* passwords that could be supplied to the buggy algorithm. Our safety measure
|
||||||
|
* is meant to protect from such many-buggy to one-correct collisions, by
|
||||||
|
* deviating from the correct algorithm in such cases. Let's check for this.
|
||||||
|
*/
|
||||||
|
diff |= diff >> 16; /* still zero iff exact match */
|
||||||
|
diff &= 0xffff; /* ditto */
|
||||||
|
diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */
|
||||||
|
sign <<= 9; /* move the non-benign sign extension flag to bit 16 */
|
||||||
|
sign &= ~diff & safety; /* action needed? */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we have determined that we need to deviate from the correct algorithm,
|
||||||
|
* flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but
|
||||||
|
* let's stick to it now. It came out of the approach we used above, and it's
|
||||||
|
* not any worse than any other choice we could make.)
|
||||||
|
*
|
||||||
|
* It is crucial that we don't do the same to the expanded key used in the main
|
||||||
|
* Eksblowfish loop. By doing it to only one of these two, we deviate from a
|
||||||
|
* state that could be directly specified by a password to the buggy algorithm
|
||||||
|
* (and to the fully correct one as well, but that's a side-effect).
|
||||||
|
*/
|
||||||
|
initial[0] ^= sign;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const unsigned char flags_by_subtype[26] =
|
||||||
|
{2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0};
|
||||||
|
|
||||||
|
static char *BF_crypt(const char *key, const char *setting,
|
||||||
|
char *output, int size,
|
||||||
|
BF_word min)
|
||||||
|
{
|
||||||
|
#if BF_ASM
|
||||||
|
extern void _BF_body_r(BF_ctx *ctx);
|
||||||
|
#endif
|
||||||
|
struct {
|
||||||
|
BF_ctx ctx;
|
||||||
|
BF_key expanded_key;
|
||||||
|
union {
|
||||||
|
BF_word salt[4];
|
||||||
|
BF_word output[6];
|
||||||
|
} binary;
|
||||||
|
} data;
|
||||||
|
BF_word L, R;
|
||||||
|
BF_word tmp1, tmp2, tmp3, tmp4;
|
||||||
|
BF_word *ptr;
|
||||||
|
BF_word count;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (size < 7 + 22 + 31 + 1) {
|
||||||
|
__set_errno(ERANGE);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setting[0] != '$' ||
|
||||||
|
setting[1] != '2' ||
|
||||||
|
setting[2] < 'a' || setting[2] > 'z' ||
|
||||||
|
!flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] ||
|
||||||
|
setting[3] != '$' ||
|
||||||
|
setting[4] < '0' || setting[4] > '3' ||
|
||||||
|
setting[5] < '0' || setting[5] > '9' ||
|
||||||
|
(setting[4] == '3' && setting[5] > '1') ||
|
||||||
|
setting[6] != '$') {
|
||||||
|
__set_errno(EINVAL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0'));
|
||||||
|
if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) {
|
||||||
|
__set_errno(EINVAL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
BF_swap(data.binary.salt, 4);
|
||||||
|
|
||||||
|
BF_set_key(key, data.expanded_key, data.ctx.P,
|
||||||
|
flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']);
|
||||||
|
|
||||||
|
memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S));
|
||||||
|
|
||||||
|
L = R = 0;
|
||||||
|
for (i = 0; i < BF_N + 2; i += 2) {
|
||||||
|
L ^= data.binary.salt[i & 2];
|
||||||
|
R ^= data.binary.salt[(i & 2) + 1];
|
||||||
|
BF_ENCRYPT;
|
||||||
|
data.ctx.P[i] = L;
|
||||||
|
data.ctx.P[i + 1] = R;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr = data.ctx.S[0];
|
||||||
|
do {
|
||||||
|
ptr += 4;
|
||||||
|
L ^= data.binary.salt[(BF_N + 2) & 3];
|
||||||
|
R ^= data.binary.salt[(BF_N + 3) & 3];
|
||||||
|
BF_ENCRYPT;
|
||||||
|
*(ptr - 4) = L;
|
||||||
|
*(ptr - 3) = R;
|
||||||
|
|
||||||
|
L ^= data.binary.salt[(BF_N + 4) & 3];
|
||||||
|
R ^= data.binary.salt[(BF_N + 5) & 3];
|
||||||
|
BF_ENCRYPT;
|
||||||
|
*(ptr - 2) = L;
|
||||||
|
*(ptr - 1) = R;
|
||||||
|
} while (ptr < &data.ctx.S[3][0xFF]);
|
||||||
|
|
||||||
|
do {
|
||||||
|
int done;
|
||||||
|
|
||||||
|
for (i = 0; i < BF_N + 2; i += 2) {
|
||||||
|
data.ctx.P[i] ^= data.expanded_key[i];
|
||||||
|
data.ctx.P[i + 1] ^= data.expanded_key[i + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
done = 0;
|
||||||
|
do {
|
||||||
|
BF_body();
|
||||||
|
if (done)
|
||||||
|
break;
|
||||||
|
done = 1;
|
||||||
|
|
||||||
|
tmp1 = data.binary.salt[0];
|
||||||
|
tmp2 = data.binary.salt[1];
|
||||||
|
tmp3 = data.binary.salt[2];
|
||||||
|
tmp4 = data.binary.salt[3];
|
||||||
|
for (i = 0; i < BF_N; i += 4) {
|
||||||
|
data.ctx.P[i] ^= tmp1;
|
||||||
|
data.ctx.P[i + 1] ^= tmp2;
|
||||||
|
data.ctx.P[i + 2] ^= tmp3;
|
||||||
|
data.ctx.P[i + 3] ^= tmp4;
|
||||||
|
}
|
||||||
|
data.ctx.P[16] ^= tmp1;
|
||||||
|
data.ctx.P[17] ^= tmp2;
|
||||||
|
} while (1);
|
||||||
|
} while (--count);
|
||||||
|
|
||||||
|
for (i = 0; i < 6; i += 2) {
|
||||||
|
L = BF_magic_w[i];
|
||||||
|
R = BF_magic_w[i + 1];
|
||||||
|
|
||||||
|
count = 64;
|
||||||
|
do {
|
||||||
|
BF_ENCRYPT;
|
||||||
|
} while (--count);
|
||||||
|
|
||||||
|
data.binary.output[i] = L;
|
||||||
|
data.binary.output[i + 1] = R;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(output, setting, 7 + 22 - 1);
|
||||||
|
output[7 + 22 - 1] = BF_itoa64[(int)
|
||||||
|
BF_atoi64[(int)setting[7 + 22 - 1] - 0x20] & 0x30];
|
||||||
|
|
||||||
|
/* This has to be bug-compatible with the original implementation, so
|
||||||
|
* only encode 23 of the 24 bytes. :-) */
|
||||||
|
BF_swap(data.binary.output, 6);
|
||||||
|
BF_encode(&output[7 + 22], data.binary.output, 23);
|
||||||
|
output[7 + 22 + 31] = '\0';
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _crypt_output_magic(const char *setting, char *output, int size)
|
||||||
|
{
|
||||||
|
if (size < 3)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
output[0] = '*';
|
||||||
|
output[1] = '0';
|
||||||
|
output[2] = '\0';
|
||||||
|
|
||||||
|
if (setting[0] == '*' && setting[1] == '0')
|
||||||
|
output[1] = '1';
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Please preserve the runtime self-test. It serves two purposes at once:
|
||||||
|
*
|
||||||
|
* 1. We really can't afford the risk of producing incompatible hashes e.g.
|
||||||
|
* when there's something like gcc bug 26587 again, whereas an application or
|
||||||
|
* library integrating this code might not also integrate our external tests or
|
||||||
|
* it might not run them after every build. Even if it does, the miscompile
|
||||||
|
* might only occur on the production build, but not on a testing build (such
|
||||||
|
* as because of different optimization settings). It is painful to recover
|
||||||
|
* from incorrectly-computed hashes - merely fixing whatever broke is not
|
||||||
|
* enough. Thus, a proactive measure like this self-test is needed.
|
||||||
|
*
|
||||||
|
* 2. We don't want to leave sensitive data from our actual password hash
|
||||||
|
* computation on the stack or in registers. Previous revisions of the code
|
||||||
|
* would do explicit cleanups, but simply running the self-test after hash
|
||||||
|
* computation is more reliable.
|
||||||
|
*
|
||||||
|
* The performance cost of this quick self-test is around 0.6% at the "$2a$08"
|
||||||
|
* setting.
|
||||||
|
*/
|
||||||
|
char *_crypt_blowfish_rn(const char *key, const char *setting,
|
||||||
|
char *output, int size)
|
||||||
|
{
|
||||||
|
const char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8";
|
||||||
|
const char *test_setting = "$2a$00$abcdefghijklmnopqrstuu";
|
||||||
|
static const char * const test_hashes[2] =
|
||||||
|
{"i1D709vfamulimlGcq0qq3UvuUasvEa\0\x55", /* 'a', 'b', 'y' */
|
||||||
|
"VUrPmXD6q/nVSSp7pNDhCR9071IfIRe\0\x55"}; /* 'x' */
|
||||||
|
const char *test_hash = test_hashes[0];
|
||||||
|
char *retval;
|
||||||
|
const char *p;
|
||||||
|
int save_errno, ok;
|
||||||
|
struct {
|
||||||
|
char s[7 + 22 + 1];
|
||||||
|
char o[7 + 22 + 31 + 1 + 1 + 1];
|
||||||
|
} buf;
|
||||||
|
|
||||||
|
/* Hash the supplied password */
|
||||||
|
_crypt_output_magic(setting, output, size);
|
||||||
|
retval = BF_crypt(key, setting, output, size, 16);
|
||||||
|
save_errno = errno;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Do a quick self-test. It is important that we make both calls to BF_crypt()
|
||||||
|
* from the same scope such that they likely use the same stack locations,
|
||||||
|
* which makes the second call overwrite the first call's sensitive data on the
|
||||||
|
* stack and makes it more likely that any alignment related issues would be
|
||||||
|
* detected by the self-test.
|
||||||
|
*/
|
||||||
|
memcpy(buf.s, test_setting, sizeof(buf.s));
|
||||||
|
if (retval) {
|
||||||
|
unsigned int flags = flags_by_subtype[
|
||||||
|
(unsigned int)(unsigned char)setting[2] - 'a'];
|
||||||
|
test_hash = test_hashes[flags & 1];
|
||||||
|
buf.s[2] = setting[2];
|
||||||
|
}
|
||||||
|
memset(buf.o, 0x55, sizeof(buf.o));
|
||||||
|
buf.o[sizeof(buf.o) - 1] = 0;
|
||||||
|
p = BF_crypt(test_key, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1);
|
||||||
|
|
||||||
|
ok = (p == buf.o &&
|
||||||
|
!memcmp(p, buf.s, 7 + 22) &&
|
||||||
|
!memcmp(p + (7 + 22), test_hash, 31 + 1 + 1 + 1));
|
||||||
|
|
||||||
|
{
|
||||||
|
const char *k = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345";
|
||||||
|
BF_key ae, ai, ye, yi;
|
||||||
|
BF_set_key(k, ae, ai, 2); /* $2a$ */
|
||||||
|
BF_set_key(k, ye, yi, 4); /* $2y$ */
|
||||||
|
ai[0] ^= 0x10000; /* undo the safety (for comparison) */
|
||||||
|
ok = ok && ai[0] == 0xdb9c59bc && ye[17] == 0x33343500 &&
|
||||||
|
!memcmp(ae, ye, sizeof(ae)) &&
|
||||||
|
!memcmp(ai, yi, sizeof(ai));
|
||||||
|
}
|
||||||
|
|
||||||
|
__set_errno(save_errno);
|
||||||
|
if (ok)
|
||||||
|
return retval;
|
||||||
|
|
||||||
|
/* Should not happen */
|
||||||
|
_crypt_output_magic(setting, output, size);
|
||||||
|
__set_errno(EINVAL); /* pretend we don't support this hash type */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count,
|
||||||
|
const char *input, int size, char *output, int output_size)
|
||||||
|
{
|
||||||
|
if (size < 16 || output_size < 7 + 22 + 1 ||
|
||||||
|
(count && (count < 4 || count > 31)) ||
|
||||||
|
prefix[0] != '$' || prefix[1] != '2' ||
|
||||||
|
(prefix[2] != 'a' && prefix[2] != 'b' && prefix[2] != 'y')) {
|
||||||
|
if (output_size > 0) output[0] = '\0';
|
||||||
|
__set_errno((output_size < 7 + 22 + 1) ? ERANGE : EINVAL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!count) count = 5;
|
||||||
|
|
||||||
|
output[0] = '$';
|
||||||
|
output[1] = '2';
|
||||||
|
output[2] = prefix[2];
|
||||||
|
output[3] = '$';
|
||||||
|
output[4] = '0' + count / 10;
|
||||||
|
output[5] = '0' + count % 10;
|
||||||
|
output[6] = '$';
|
||||||
|
|
||||||
|
BF_encode(&output[7], (const BF_word *)input, 16);
|
||||||
|
output[7 + 22] = '\0';
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
/*
|
||||||
|
* Written by Solar Designer <solar at openwall.com> in 2000-2011.
|
||||||
|
* No copyright is claimed, and the software is hereby placed in the public
|
||||||
|
* domain. In case this attempt to disclaim copyright and place the software
|
||||||
|
* in the public domain is deemed null and void, then the software is
|
||||||
|
* Copyright (c) 2000-2011 Solar Designer and it is hereby released to the
|
||||||
|
* general public under the following terms:
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted.
|
||||||
|
*
|
||||||
|
* There's ABSOLUTELY NO WARRANTY, express or implied.
|
||||||
|
*
|
||||||
|
* See crypt_blowfish.c for more information.
|
||||||
|
*
|
||||||
|
* This file contains salt generation functions for the traditional and
|
||||||
|
* other common crypt(3) algorithms, except for bcrypt which is defined
|
||||||
|
* entirely in crypt_blowfish.c.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#ifndef __set_errno
|
||||||
|
#define __set_errno(val) errno = (val)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Just to make sure the prototypes match the actual definitions */
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include "../include/bcrypt/crypt_gensalt.h"
|
||||||
|
#else
|
||||||
|
#include "crypt_gensalt.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
unsigned char _crypt_itoa64[64 + 1] =
|
||||||
|
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||||
|
|
||||||
|
char *_crypt_gensalt_traditional_rn(const char *prefix, unsigned long count,
|
||||||
|
const char *input, int size, char *output, int output_size)
|
||||||
|
{
|
||||||
|
(void) prefix;
|
||||||
|
|
||||||
|
if (size < 2 || output_size < 2 + 1 || (count && count != 25)) {
|
||||||
|
if (output_size > 0) output[0] = '\0';
|
||||||
|
__set_errno((output_size < 2 + 1) ? ERANGE : EINVAL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
output[0] = _crypt_itoa64[(unsigned int)input[0] & 0x3f];
|
||||||
|
output[1] = _crypt_itoa64[(unsigned int)input[1] & 0x3f];
|
||||||
|
output[2] = '\0';
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *_crypt_gensalt_extended_rn(const char *prefix, unsigned long count,
|
||||||
|
const char *input, int size, char *output, int output_size)
|
||||||
|
{
|
||||||
|
unsigned long value;
|
||||||
|
|
||||||
|
(void) prefix;
|
||||||
|
|
||||||
|
/* Even iteration counts make it easier to detect weak DES keys from a look
|
||||||
|
* at the hash, so they should be avoided */
|
||||||
|
if (size < 3 || output_size < 1 + 4 + 4 + 1 ||
|
||||||
|
(count && (count > 0xffffff || !(count & 1)))) {
|
||||||
|
if (output_size > 0) output[0] = '\0';
|
||||||
|
__set_errno((output_size < 1 + 4 + 4 + 1) ? ERANGE : EINVAL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!count) count = 725;
|
||||||
|
|
||||||
|
output[0] = '_';
|
||||||
|
output[1] = _crypt_itoa64[count & 0x3f];
|
||||||
|
output[2] = _crypt_itoa64[(count >> 6) & 0x3f];
|
||||||
|
output[3] = _crypt_itoa64[(count >> 12) & 0x3f];
|
||||||
|
output[4] = _crypt_itoa64[(count >> 18) & 0x3f];
|
||||||
|
value = (unsigned long)(unsigned char)input[0] |
|
||||||
|
((unsigned long)(unsigned char)input[1] << 8) |
|
||||||
|
((unsigned long)(unsigned char)input[2] << 16);
|
||||||
|
output[5] = _crypt_itoa64[value & 0x3f];
|
||||||
|
output[6] = _crypt_itoa64[(value >> 6) & 0x3f];
|
||||||
|
output[7] = _crypt_itoa64[(value >> 12) & 0x3f];
|
||||||
|
output[8] = _crypt_itoa64[(value >> 18) & 0x3f];
|
||||||
|
output[9] = '\0';
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *_crypt_gensalt_md5_rn(const char *prefix, unsigned long count,
|
||||||
|
const char *input, int size, char *output, int output_size)
|
||||||
|
{
|
||||||
|
unsigned long value;
|
||||||
|
|
||||||
|
(void) prefix;
|
||||||
|
|
||||||
|
if (size < 3 || output_size < 3 + 4 + 1 || (count && count != 1000)) {
|
||||||
|
if (output_size > 0) output[0] = '\0';
|
||||||
|
__set_errno((output_size < 3 + 4 + 1) ? ERANGE : EINVAL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
output[0] = '$';
|
||||||
|
output[1] = '1';
|
||||||
|
output[2] = '$';
|
||||||
|
value = (unsigned long)(unsigned char)input[0] |
|
||||||
|
((unsigned long)(unsigned char)input[1] << 8) |
|
||||||
|
((unsigned long)(unsigned char)input[2] << 16);
|
||||||
|
output[3] = _crypt_itoa64[value & 0x3f];
|
||||||
|
output[4] = _crypt_itoa64[(value >> 6) & 0x3f];
|
||||||
|
output[5] = _crypt_itoa64[(value >> 12) & 0x3f];
|
||||||
|
output[6] = _crypt_itoa64[(value >> 18) & 0x3f];
|
||||||
|
output[7] = '\0';
|
||||||
|
|
||||||
|
if (size >= 6 && output_size >= 3 + 4 + 4 + 1) {
|
||||||
|
value = (unsigned long)(unsigned char)input[3] |
|
||||||
|
((unsigned long)(unsigned char)input[4] << 8) |
|
||||||
|
((unsigned long)(unsigned char)input[5] << 16);
|
||||||
|
output[7] = _crypt_itoa64[value & 0x3f];
|
||||||
|
output[8] = _crypt_itoa64[(value >> 6) & 0x3f];
|
||||||
|
output[9] = _crypt_itoa64[(value >> 12) & 0x3f];
|
||||||
|
output[10] = _crypt_itoa64[(value >> 18) & 0x3f];
|
||||||
|
output[11] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
#include "bcrypt/BCrypt.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
std::string right_password = "right_password";
|
||||||
|
std::string wrong_password = "wrong_password";
|
||||||
|
|
||||||
|
std::cout << "generate hash... " << std::flush;
|
||||||
|
std::string hash = BCrypt::generateHash(right_password, 12);
|
||||||
|
std::cout << "done." << std::endl;
|
||||||
|
|
||||||
|
std::cout << "checking right password: " << std::flush
|
||||||
|
<< BCrypt::validatePassword(right_password,hash) << std::endl;
|
||||||
|
|
||||||
|
std::cout << "checking wrong password: " << std::flush
|
||||||
|
<< BCrypt::validatePassword(wrong_password,hash) << std::endl;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,563 @@
|
||||||
|
/*
|
||||||
|
* Written by Solar Designer <solar at openwall.com> in 2000-2014.
|
||||||
|
* No copyright is claimed, and the software is hereby placed in the public
|
||||||
|
* domain. In case this attempt to disclaim copyright and place the software
|
||||||
|
* in the public domain is deemed null and void, then the software is
|
||||||
|
* Copyright (c) 2000-2014 Solar Designer and it is hereby released to the
|
||||||
|
* general public under the following terms:
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted.
|
||||||
|
*
|
||||||
|
* There's ABSOLUTELY NO WARRANTY, express or implied.
|
||||||
|
*
|
||||||
|
* See crypt_blowfish.c for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#ifndef __set_errno
|
||||||
|
#define __set_errno(val) errno = (val)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef TEST
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/times.h>
|
||||||
|
#ifdef TEST_THREADS
|
||||||
|
#include <pthread.h>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define CRYPT_OUTPUT_SIZE (7 + 22 + 31 + 1)
|
||||||
|
#define CRYPT_GENSALT_OUTPUT_SIZE (7 + 22 + 1)
|
||||||
|
|
||||||
|
#if defined(__GLIBC__) && defined(_LIBC)
|
||||||
|
#define __SKIP_GNU
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WIN32 | _WIN64
|
||||||
|
#include "../include/bcrypt/ow-crypt.h"
|
||||||
|
|
||||||
|
#include "../include/bcrypt/crypt_blowfish.h"
|
||||||
|
#include "../include/bcrypt/crypt_gensalt.h"
|
||||||
|
#else
|
||||||
|
#include "ow-crypt.h"
|
||||||
|
|
||||||
|
#include "crypt_blowfish.h"
|
||||||
|
#include "crypt_gensalt.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__GLIBC__) && defined(_LIBC)
|
||||||
|
/* crypt.h from glibc-crypt-2.1 will define struct crypt_data for us */
|
||||||
|
#include "crypt.h"
|
||||||
|
extern char *__md5_crypt_r(const char *key, const char *salt,
|
||||||
|
char *buffer, int buflen);
|
||||||
|
/* crypt-entry.c needs to be patched to define __des_crypt_r rather than
|
||||||
|
* __crypt_r, and not define crypt_r and crypt at all */
|
||||||
|
extern char *__des_crypt_r(const char *key, const char *salt,
|
||||||
|
struct crypt_data *data);
|
||||||
|
extern struct crypt_data _ufc_foobar;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int _crypt_data_alloc(void **data, int *size, int need)
|
||||||
|
{
|
||||||
|
void *updated;
|
||||||
|
|
||||||
|
if (*data && *size >= need) return 0;
|
||||||
|
|
||||||
|
updated = realloc(*data, need);
|
||||||
|
|
||||||
|
if (!updated) {
|
||||||
|
#ifndef __GLIBC__
|
||||||
|
/* realloc(3) on glibc sets errno, so we don't need to bother */
|
||||||
|
__set_errno(ENOMEM);
|
||||||
|
#endif
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__GLIBC__) && defined(_LIBC)
|
||||||
|
if (need >= sizeof(struct crypt_data))
|
||||||
|
((struct crypt_data *)updated)->initialized = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
*data = updated;
|
||||||
|
*size = need;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *_crypt_retval_magic(char *retval, const char *setting,
|
||||||
|
char *output, int size)
|
||||||
|
{
|
||||||
|
if (retval)
|
||||||
|
return retval;
|
||||||
|
|
||||||
|
if (_crypt_output_magic(setting, output, size))
|
||||||
|
return NULL; /* shouldn't happen */
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__GLIBC__) && defined(_LIBC)
|
||||||
|
/*
|
||||||
|
* Applications may re-use the same instance of struct crypt_data without
|
||||||
|
* resetting the initialized field in order to let crypt_r() skip some of
|
||||||
|
* its initialization code. Thus, it is important that our multiple hashing
|
||||||
|
* algorithms either don't conflict with each other in their use of the
|
||||||
|
* data area or reset the initialized field themselves whenever required.
|
||||||
|
* Currently, the hashing algorithms simply have no conflicts: the first
|
||||||
|
* field of struct crypt_data is the 128-byte large DES key schedule which
|
||||||
|
* __des_crypt_r() calculates each time it is called while the two other
|
||||||
|
* hashing algorithms use less than 128 bytes of the data area.
|
||||||
|
*/
|
||||||
|
|
||||||
|
char *__crypt_rn(__const char *key, __const char *setting,
|
||||||
|
void *data, int size)
|
||||||
|
{
|
||||||
|
if (setting[0] == '$' && setting[1] == '2')
|
||||||
|
return _crypt_blowfish_rn(key, setting, (char *)data, size);
|
||||||
|
if (setting[0] == '$' && setting[1] == '1')
|
||||||
|
return __md5_crypt_r(key, setting, (char *)data, size);
|
||||||
|
if (setting[0] == '$' || setting[0] == '_') {
|
||||||
|
__set_errno(EINVAL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (size >= sizeof(struct crypt_data))
|
||||||
|
return __des_crypt_r(key, setting, (struct crypt_data *)data);
|
||||||
|
__set_errno(ERANGE);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *__crypt_ra(__const char *key, __const char *setting,
|
||||||
|
void **data, int *size)
|
||||||
|
{
|
||||||
|
if (setting[0] == '$' && setting[1] == '2') {
|
||||||
|
if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE))
|
||||||
|
return NULL;
|
||||||
|
return _crypt_blowfish_rn(key, setting, (char *)*data, *size);
|
||||||
|
}
|
||||||
|
if (setting[0] == '$' && setting[1] == '1') {
|
||||||
|
if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE))
|
||||||
|
return NULL;
|
||||||
|
return __md5_crypt_r(key, setting, (char *)*data, *size);
|
||||||
|
}
|
||||||
|
if (setting[0] == '$' || setting[0] == '_') {
|
||||||
|
__set_errno(EINVAL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (_crypt_data_alloc(data, size, sizeof(struct crypt_data)))
|
||||||
|
return NULL;
|
||||||
|
return __des_crypt_r(key, setting, (struct crypt_data *)*data);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *__crypt_r(__const char *key, __const char *setting,
|
||||||
|
struct crypt_data *data)
|
||||||
|
{
|
||||||
|
return _crypt_retval_magic(
|
||||||
|
__crypt_rn(key, setting, data, sizeof(*data)),
|
||||||
|
setting, (char *)data, sizeof(*data));
|
||||||
|
}
|
||||||
|
|
||||||
|
char *__crypt(__const char *key, __const char *setting)
|
||||||
|
{
|
||||||
|
return _crypt_retval_magic(
|
||||||
|
__crypt_rn(key, setting, &_ufc_foobar, sizeof(_ufc_foobar)),
|
||||||
|
setting, (char *)&_ufc_foobar, sizeof(_ufc_foobar));
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
char *crypt_rn(const char *key, const char *setting, void *data, int size)
|
||||||
|
{
|
||||||
|
return _crypt_blowfish_rn(key, setting, (char *)data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *crypt_ra(const char *key, const char *setting,
|
||||||
|
void **data, int *size)
|
||||||
|
{
|
||||||
|
if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE))
|
||||||
|
return NULL;
|
||||||
|
return _crypt_blowfish_rn(key, setting, (char *)*data, *size);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *crypt_r(const char *key, const char *setting, void *data)
|
||||||
|
{
|
||||||
|
return _crypt_retval_magic(
|
||||||
|
crypt_rn(key, setting, data, CRYPT_OUTPUT_SIZE),
|
||||||
|
setting, (char *)data, CRYPT_OUTPUT_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *crypt(const char *key, const char *setting)
|
||||||
|
{
|
||||||
|
static char output[CRYPT_OUTPUT_SIZE];
|
||||||
|
|
||||||
|
return _crypt_retval_magic(
|
||||||
|
crypt_rn(key, setting, output, sizeof(output)),
|
||||||
|
setting, output, sizeof(output));
|
||||||
|
}
|
||||||
|
|
||||||
|
#define __crypt_gensalt_rn crypt_gensalt_rn
|
||||||
|
#define __crypt_gensalt_ra crypt_gensalt_ra
|
||||||
|
#define __crypt_gensalt crypt_gensalt
|
||||||
|
#endif
|
||||||
|
|
||||||
|
char *__crypt_gensalt_rn(const char *prefix, unsigned long count,
|
||||||
|
const char *input, int size, char *output, int output_size)
|
||||||
|
{
|
||||||
|
char *(*use)(const char *_prefix, unsigned long _count,
|
||||||
|
const char *_input, int _size,
|
||||||
|
char *_output, int _output_size);
|
||||||
|
|
||||||
|
/* This may be supported on some platforms in the future */
|
||||||
|
if (!input) {
|
||||||
|
__set_errno(EINVAL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strncmp(prefix, "$2a$", 4) || !strncmp(prefix, "$2b$", 4) ||
|
||||||
|
!strncmp(prefix, "$2y$", 4))
|
||||||
|
use = _crypt_gensalt_blowfish_rn;
|
||||||
|
else
|
||||||
|
if (!strncmp(prefix, "$1$", 3))
|
||||||
|
use = _crypt_gensalt_md5_rn;
|
||||||
|
else
|
||||||
|
if (prefix[0] == '_')
|
||||||
|
use = _crypt_gensalt_extended_rn;
|
||||||
|
else
|
||||||
|
if (!prefix[0] ||
|
||||||
|
(prefix[0] && prefix[1] &&
|
||||||
|
memchr(_crypt_itoa64, prefix[0], 64) &&
|
||||||
|
memchr(_crypt_itoa64, prefix[1], 64)))
|
||||||
|
use = _crypt_gensalt_traditional_rn;
|
||||||
|
else {
|
||||||
|
__set_errno(EINVAL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return use(prefix, count, input, size, output, output_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *__crypt_gensalt_ra(const char *prefix, unsigned long count,
|
||||||
|
const char *input, int size)
|
||||||
|
{
|
||||||
|
char output[CRYPT_GENSALT_OUTPUT_SIZE];
|
||||||
|
char *retval;
|
||||||
|
|
||||||
|
retval = __crypt_gensalt_rn(prefix, count,
|
||||||
|
input, size, output, sizeof(output));
|
||||||
|
|
||||||
|
if (retval) {
|
||||||
|
#ifdef _WIN32 | _WIN64
|
||||||
|
retval = _strdup(retval);
|
||||||
|
#else
|
||||||
|
retval = strdup(retval);
|
||||||
|
#endif
|
||||||
|
#ifndef __GLIBC__
|
||||||
|
/* strdup(3) on glibc sets errno, so we don't need to bother */
|
||||||
|
if (!retval)
|
||||||
|
__set_errno(ENOMEM);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *__crypt_gensalt(const char *prefix, unsigned long count,
|
||||||
|
const char *input, int size)
|
||||||
|
{
|
||||||
|
static char output[CRYPT_GENSALT_OUTPUT_SIZE];
|
||||||
|
|
||||||
|
return __crypt_gensalt_rn(prefix, count,
|
||||||
|
input, size, output, sizeof(output));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__GLIBC__) && defined(_LIBC)
|
||||||
|
weak_alias(__crypt_rn, crypt_rn)
|
||||||
|
weak_alias(__crypt_ra, crypt_ra)
|
||||||
|
weak_alias(__crypt_r, crypt_r)
|
||||||
|
weak_alias(__crypt, crypt)
|
||||||
|
weak_alias(__crypt_gensalt_rn, crypt_gensalt_rn)
|
||||||
|
weak_alias(__crypt_gensalt_ra, crypt_gensalt_ra)
|
||||||
|
weak_alias(__crypt_gensalt, crypt_gensalt)
|
||||||
|
weak_alias(crypt, fcrypt)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef TEST
|
||||||
|
static const char *tests[][3] = {
|
||||||
|
{"$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW",
|
||||||
|
"U*U"},
|
||||||
|
{"$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK",
|
||||||
|
"U*U*"},
|
||||||
|
{"$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a",
|
||||||
|
"U*U*U"},
|
||||||
|
{"$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui",
|
||||||
|
"0123456789abcdefghijklmnopqrstuvwxyz"
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
"chars after 72 are ignored"},
|
||||||
|
{"$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e",
|
||||||
|
"\xa3"},
|
||||||
|
{"$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e",
|
||||||
|
"\xff\xff\xa3"},
|
||||||
|
{"$2y$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e",
|
||||||
|
"\xff\xff\xa3"},
|
||||||
|
{"$2a$05$/OK.fbVrR/bpIqNJ5ianF.nqd1wy.pTMdcvrRWxyiGL2eMz.2a85.",
|
||||||
|
"\xff\xff\xa3"},
|
||||||
|
{"$2b$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e",
|
||||||
|
"\xff\xff\xa3"},
|
||||||
|
{"$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq",
|
||||||
|
"\xa3"},
|
||||||
|
{"$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq",
|
||||||
|
"\xa3"},
|
||||||
|
{"$2b$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq",
|
||||||
|
"\xa3"},
|
||||||
|
{"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi",
|
||||||
|
"1\xa3" "345"},
|
||||||
|
{"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi",
|
||||||
|
"\xff\xa3" "345"},
|
||||||
|
{"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi",
|
||||||
|
"\xff\xa3" "34" "\xff\xff\xff\xa3" "345"},
|
||||||
|
{"$2y$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi",
|
||||||
|
"\xff\xa3" "34" "\xff\xff\xff\xa3" "345"},
|
||||||
|
{"$2a$05$/OK.fbVrR/bpIqNJ5ianF.ZC1JEJ8Z4gPfpe1JOr/oyPXTWl9EFd.",
|
||||||
|
"\xff\xa3" "34" "\xff\xff\xff\xa3" "345"},
|
||||||
|
{"$2y$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e",
|
||||||
|
"\xff\xa3" "345"},
|
||||||
|
{"$2a$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e",
|
||||||
|
"\xff\xa3" "345"},
|
||||||
|
{"$2a$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS",
|
||||||
|
"\xa3" "ab"},
|
||||||
|
{"$2x$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS",
|
||||||
|
"\xa3" "ab"},
|
||||||
|
{"$2y$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS",
|
||||||
|
"\xa3" "ab"},
|
||||||
|
{"$2x$05$6bNw2HLQYeqHYyBfLMsv/OiwqTymGIGzFsA4hOTWebfehXHNprcAS",
|
||||||
|
"\xd1\x91"},
|
||||||
|
{"$2x$05$6bNw2HLQYeqHYyBfLMsv/O9LIGgn8OMzuDoHfof8AQimSGfcSWxnS",
|
||||||
|
"\xd0\xc1\xd2\xcf\xcc\xd8"},
|
||||||
|
{"$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6",
|
||||||
|
"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
|
||||||
|
"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
|
||||||
|
"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
|
||||||
|
"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
|
||||||
|
"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
|
||||||
|
"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
|
||||||
|
"chars after 72 are ignored as usual"},
|
||||||
|
{"$2a$05$/OK.fbVrR/bpIqNJ5ianF.R9xrDjiycxMbQE2bp.vgqlYpW5wx2yy",
|
||||||
|
"\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55"
|
||||||
|
"\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55"
|
||||||
|
"\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55"
|
||||||
|
"\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55"
|
||||||
|
"\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55"
|
||||||
|
"\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55"},
|
||||||
|
{"$2a$05$/OK.fbVrR/bpIqNJ5ianF.9tQZzcJfm3uj2NvJ/n5xkhpqLrMpWCe",
|
||||||
|
"\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff"
|
||||||
|
"\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff"
|
||||||
|
"\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff"
|
||||||
|
"\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff"
|
||||||
|
"\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff"
|
||||||
|
"\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff"},
|
||||||
|
{"$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy",
|
||||||
|
""},
|
||||||
|
{"*0", "", "$2a$03$CCCCCCCCCCCCCCCCCCCCC."},
|
||||||
|
{"*0", "", "$2a$32$CCCCCCCCCCCCCCCCCCCCC."},
|
||||||
|
{"*0", "", "$2c$05$CCCCCCCCCCCCCCCCCCCCC."},
|
||||||
|
{"*0", "", "$2z$05$CCCCCCCCCCCCCCCCCCCCC."},
|
||||||
|
{"*0", "", "$2`$05$CCCCCCCCCCCCCCCCCCCCC."},
|
||||||
|
{"*0", "", "$2{$05$CCCCCCCCCCCCCCCCCCCCC."},
|
||||||
|
{"*1", "", "*0"},
|
||||||
|
{NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
#define which tests[0]
|
||||||
|
|
||||||
|
static volatile sig_atomic_t running;
|
||||||
|
|
||||||
|
static void handle_timer(int signum)
|
||||||
|
{
|
||||||
|
(void) signum;
|
||||||
|
running = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *run(void *arg)
|
||||||
|
{
|
||||||
|
unsigned long count = 0;
|
||||||
|
int i = 0;
|
||||||
|
void *data = NULL;
|
||||||
|
int size = 0x12345678;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const char *hash = tests[i][0];
|
||||||
|
const char *key = tests[i][1];
|
||||||
|
const char *setting = tests[i][2];
|
||||||
|
|
||||||
|
if (!tests[++i][0])
|
||||||
|
i = 0;
|
||||||
|
|
||||||
|
if (setting && strlen(hash) < 30) /* not for benchmark */
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (strcmp(crypt_ra(key, hash, &data, &size), hash)) {
|
||||||
|
printf("%d: FAILED (crypt_ra/%d/%lu)\n",
|
||||||
|
(int)((char *)arg - (char *)0), i, count);
|
||||||
|
free(data);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
} while (running);
|
||||||
|
|
||||||
|
free(data);
|
||||||
|
return count + (char *)0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
struct itimerval it;
|
||||||
|
struct tms buf;
|
||||||
|
clock_t clk_tck, start_real, start_virtual, end_real, end_virtual;
|
||||||
|
unsigned long count;
|
||||||
|
void *data;
|
||||||
|
int size;
|
||||||
|
char *setting1, *setting2;
|
||||||
|
int i;
|
||||||
|
#ifdef TEST_THREADS
|
||||||
|
pthread_t t[TEST_THREADS];
|
||||||
|
void *t_retval;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
data = NULL;
|
||||||
|
size = 0x12345678;
|
||||||
|
|
||||||
|
for (i = 0; tests[i][0]; i++) {
|
||||||
|
const char *hash = tests[i][0];
|
||||||
|
const char *key = tests[i][1];
|
||||||
|
const char *setting = tests[i][2];
|
||||||
|
const char *p;
|
||||||
|
int ok = !setting || strlen(hash) >= 30;
|
||||||
|
int o_size;
|
||||||
|
char s_buf[30], o_buf[61];
|
||||||
|
if (!setting) {
|
||||||
|
memcpy(s_buf, hash, sizeof(s_buf) - 1);
|
||||||
|
s_buf[sizeof(s_buf) - 1] = 0;
|
||||||
|
setting = s_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
__set_errno(0);
|
||||||
|
p = crypt(key, setting);
|
||||||
|
if ((!ok && !errno) || strcmp(p, hash)) {
|
||||||
|
printf("FAILED (crypt/%d)\n", i);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ok && strcmp(crypt(key, hash), hash)) {
|
||||||
|
printf("FAILED (crypt/%d)\n", i);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (o_size = -1; o_size <= (int)sizeof(o_buf); o_size++) {
|
||||||
|
int ok_n = ok && o_size == (int)sizeof(o_buf);
|
||||||
|
const char *x = "abc";
|
||||||
|
strcpy(o_buf, x);
|
||||||
|
if (o_size >= 3) {
|
||||||
|
x = "*0";
|
||||||
|
if (setting[0] == '*' && setting[1] == '0')
|
||||||
|
x = "*1";
|
||||||
|
}
|
||||||
|
__set_errno(0);
|
||||||
|
p = crypt_rn(key, setting, o_buf, o_size);
|
||||||
|
if ((ok_n && (!p || strcmp(p, hash))) ||
|
||||||
|
(!ok_n && (!errno || p || strcmp(o_buf, x)))) {
|
||||||
|
printf("FAILED (crypt_rn/%d)\n", i);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__set_errno(0);
|
||||||
|
p = crypt_ra(key, setting, &data, &size);
|
||||||
|
if ((ok && (!p || strcmp(p, hash))) ||
|
||||||
|
(!ok && (!errno || p || strcmp((char *)data, hash)))) {
|
||||||
|
printf("FAILED (crypt_ra/%d)\n", i);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setting1 = crypt_gensalt(which[0], 12, data, size);
|
||||||
|
if (!setting1 || strncmp(setting1, "$2a$12$", 7)) {
|
||||||
|
puts("FAILED (crypt_gensalt)\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
setting2 = crypt_gensalt_ra(setting1, 12, data, size);
|
||||||
|
if (strcmp(setting1, setting2)) {
|
||||||
|
puts("FAILED (crypt_gensalt_ra/1)\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*(char *)data)++;
|
||||||
|
setting1 = crypt_gensalt_ra(setting2, 12, data, size);
|
||||||
|
if (!strcmp(setting1, setting2)) {
|
||||||
|
puts("FAILED (crypt_gensalt_ra/2)\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(setting1);
|
||||||
|
free(setting2);
|
||||||
|
free(data);
|
||||||
|
|
||||||
|
#if defined(_SC_CLK_TCK) || !defined(CLK_TCK)
|
||||||
|
clk_tck = sysconf(_SC_CLK_TCK);
|
||||||
|
#else
|
||||||
|
clk_tck = CLK_TCK;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
running = 1;
|
||||||
|
signal(SIGALRM, handle_timer);
|
||||||
|
|
||||||
|
memset(&it, 0, sizeof(it));
|
||||||
|
it.it_value.tv_sec = 5;
|
||||||
|
setitimer(ITIMER_REAL, &it, NULL);
|
||||||
|
|
||||||
|
start_real = times(&buf);
|
||||||
|
start_virtual = buf.tms_utime + buf.tms_stime;
|
||||||
|
|
||||||
|
count = (char *)run((char *)0) - (char *)0;
|
||||||
|
|
||||||
|
end_real = times(&buf);
|
||||||
|
end_virtual = buf.tms_utime + buf.tms_stime;
|
||||||
|
if (end_virtual == start_virtual) end_virtual++;
|
||||||
|
|
||||||
|
printf("%.1f c/s real, %.1f c/s virtual\n",
|
||||||
|
(float)count * clk_tck / (end_real - start_real),
|
||||||
|
(float)count * clk_tck / (end_virtual - start_virtual));
|
||||||
|
|
||||||
|
#ifdef TEST_THREADS
|
||||||
|
running = 1;
|
||||||
|
it.it_value.tv_sec = 60;
|
||||||
|
setitimer(ITIMER_REAL, &it, NULL);
|
||||||
|
start_real = times(&buf);
|
||||||
|
|
||||||
|
for (i = 0; i < TEST_THREADS; i++)
|
||||||
|
if (pthread_create(&t[i], NULL, run, i + (char *)0)) {
|
||||||
|
perror("pthread_create");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < TEST_THREADS; i++) {
|
||||||
|
if (pthread_join(t[i], &t_retval)) {
|
||||||
|
perror("pthread_join");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!t_retval) continue;
|
||||||
|
count = (char *)t_retval - (char *)0;
|
||||||
|
end_real = times(&buf);
|
||||||
|
printf("%d: %.1f c/s real\n", i,
|
||||||
|
(float)count * clk_tck / (end_real - start_real));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,202 @@
|
||||||
|
/*
|
||||||
|
* Written by Solar Designer <solar at openwall.com> in 1998-2010.
|
||||||
|
* No copyright is claimed, and the software is hereby placed in the public
|
||||||
|
* domain. In case this attempt to disclaim copyright and place the software
|
||||||
|
* in the public domain is deemed null and void, then the software is
|
||||||
|
* Copyright (c) 1998-2010 Solar Designer and it is hereby released to the
|
||||||
|
* general public under the following terms:
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted.
|
||||||
|
*
|
||||||
|
* There's ABSOLUTELY NO WARRANTY, express or implied.
|
||||||
|
*
|
||||||
|
* See crypt_blowfish.c for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef __i386__
|
||||||
|
|
||||||
|
#if defined(__OpenBSD__) && !defined(__ELF__)
|
||||||
|
#define UNDERSCORES
|
||||||
|
#define ALIGN_LOG
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__CYGWIN32__) || defined(__MINGW32__)
|
||||||
|
#define UNDERSCORES
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __DJGPP__
|
||||||
|
#define UNDERSCORES
|
||||||
|
#define ALIGN_LOG
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef UNDERSCORES
|
||||||
|
#define _BF_body_r __BF_body_r
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ALIGN_LOG
|
||||||
|
#define DO_ALIGN(log) .align (log)
|
||||||
|
#elif defined(DUMBAS)
|
||||||
|
#define DO_ALIGN(log) .align 1 << log
|
||||||
|
#else
|
||||||
|
#define DO_ALIGN(log) .align (1 << (log))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define BF_FRAME 0x200
|
||||||
|
#define ctx %esp
|
||||||
|
|
||||||
|
#define BF_ptr (ctx)
|
||||||
|
|
||||||
|
#define S(N, r) N+BF_FRAME(ctx,r,4)
|
||||||
|
#ifdef DUMBAS
|
||||||
|
#define P(N) 0x1000+N+N+N+N+BF_FRAME(ctx)
|
||||||
|
#else
|
||||||
|
#define P(N) 0x1000+4*N+BF_FRAME(ctx)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This version of the assembly code is optimized primarily for the original
|
||||||
|
* Intel Pentium but is also careful to avoid partial register stalls on the
|
||||||
|
* Pentium Pro family of processors (tested up to Pentium III Coppermine).
|
||||||
|
*
|
||||||
|
* It is possible to do 15% faster on the Pentium Pro family and probably on
|
||||||
|
* many non-Intel x86 processors, but, unfortunately, that would make things
|
||||||
|
* twice slower for the original Pentium.
|
||||||
|
*
|
||||||
|
* An additional 2% speedup may be achieved with non-reentrant code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define L %esi
|
||||||
|
#define R %edi
|
||||||
|
#define tmp1 %eax
|
||||||
|
#define tmp1_lo %al
|
||||||
|
#define tmp2 %ecx
|
||||||
|
#define tmp2_hi %ch
|
||||||
|
#define tmp3 %edx
|
||||||
|
#define tmp3_lo %dl
|
||||||
|
#define tmp4 %ebx
|
||||||
|
#define tmp4_hi %bh
|
||||||
|
#define tmp5 %ebp
|
||||||
|
|
||||||
|
.text
|
||||||
|
|
||||||
|
#define BF_ROUND(L, R, N) \
|
||||||
|
xorl L,tmp2; \
|
||||||
|
xorl tmp1,tmp1; \
|
||||||
|
movl tmp2,L; \
|
||||||
|
shrl $16,tmp2; \
|
||||||
|
movl L,tmp4; \
|
||||||
|
movb tmp2_hi,tmp1_lo; \
|
||||||
|
andl $0xFF,tmp2; \
|
||||||
|
movb tmp4_hi,tmp3_lo; \
|
||||||
|
andl $0xFF,tmp4; \
|
||||||
|
movl S(0,tmp1),tmp1; \
|
||||||
|
movl S(0x400,tmp2),tmp5; \
|
||||||
|
addl tmp5,tmp1; \
|
||||||
|
movl S(0x800,tmp3),tmp5; \
|
||||||
|
xorl tmp5,tmp1; \
|
||||||
|
movl S(0xC00,tmp4),tmp5; \
|
||||||
|
addl tmp1,tmp5; \
|
||||||
|
movl 4+P(N),tmp2; \
|
||||||
|
xorl tmp5,R
|
||||||
|
|
||||||
|
#define BF_ENCRYPT_START \
|
||||||
|
BF_ROUND(L, R, 0); \
|
||||||
|
BF_ROUND(R, L, 1); \
|
||||||
|
BF_ROUND(L, R, 2); \
|
||||||
|
BF_ROUND(R, L, 3); \
|
||||||
|
BF_ROUND(L, R, 4); \
|
||||||
|
BF_ROUND(R, L, 5); \
|
||||||
|
BF_ROUND(L, R, 6); \
|
||||||
|
BF_ROUND(R, L, 7); \
|
||||||
|
BF_ROUND(L, R, 8); \
|
||||||
|
BF_ROUND(R, L, 9); \
|
||||||
|
BF_ROUND(L, R, 10); \
|
||||||
|
BF_ROUND(R, L, 11); \
|
||||||
|
BF_ROUND(L, R, 12); \
|
||||||
|
BF_ROUND(R, L, 13); \
|
||||||
|
BF_ROUND(L, R, 14); \
|
||||||
|
BF_ROUND(R, L, 15); \
|
||||||
|
movl BF_ptr,tmp5; \
|
||||||
|
xorl L,tmp2; \
|
||||||
|
movl P(17),L
|
||||||
|
|
||||||
|
#define BF_ENCRYPT_END \
|
||||||
|
xorl R,L; \
|
||||||
|
movl tmp2,R
|
||||||
|
|
||||||
|
DO_ALIGN(5)
|
||||||
|
.globl _BF_body_r
|
||||||
|
_BF_body_r:
|
||||||
|
movl 4(%esp),%eax
|
||||||
|
pushl %ebp
|
||||||
|
pushl %ebx
|
||||||
|
pushl %esi
|
||||||
|
pushl %edi
|
||||||
|
subl $BF_FRAME-8,%eax
|
||||||
|
xorl L,L
|
||||||
|
cmpl %esp,%eax
|
||||||
|
ja BF_die
|
||||||
|
xchgl %eax,%esp
|
||||||
|
xorl R,R
|
||||||
|
pushl %eax
|
||||||
|
leal 0x1000+BF_FRAME-4(ctx),%eax
|
||||||
|
movl 0x1000+BF_FRAME-4(ctx),tmp2
|
||||||
|
pushl %eax
|
||||||
|
xorl tmp3,tmp3
|
||||||
|
BF_loop_P:
|
||||||
|
BF_ENCRYPT_START
|
||||||
|
addl $8,tmp5
|
||||||
|
BF_ENCRYPT_END
|
||||||
|
leal 0x1000+18*4+BF_FRAME(ctx),tmp1
|
||||||
|
movl tmp5,BF_ptr
|
||||||
|
cmpl tmp5,tmp1
|
||||||
|
movl L,-8(tmp5)
|
||||||
|
movl R,-4(tmp5)
|
||||||
|
movl P(0),tmp2
|
||||||
|
ja BF_loop_P
|
||||||
|
leal BF_FRAME(ctx),tmp5
|
||||||
|
xorl tmp3,tmp3
|
||||||
|
movl tmp5,BF_ptr
|
||||||
|
BF_loop_S:
|
||||||
|
BF_ENCRYPT_START
|
||||||
|
BF_ENCRYPT_END
|
||||||
|
movl P(0),tmp2
|
||||||
|
movl L,(tmp5)
|
||||||
|
movl R,4(tmp5)
|
||||||
|
BF_ENCRYPT_START
|
||||||
|
BF_ENCRYPT_END
|
||||||
|
movl P(0),tmp2
|
||||||
|
movl L,8(tmp5)
|
||||||
|
movl R,12(tmp5)
|
||||||
|
BF_ENCRYPT_START
|
||||||
|
BF_ENCRYPT_END
|
||||||
|
movl P(0),tmp2
|
||||||
|
movl L,16(tmp5)
|
||||||
|
movl R,20(tmp5)
|
||||||
|
BF_ENCRYPT_START
|
||||||
|
addl $32,tmp5
|
||||||
|
BF_ENCRYPT_END
|
||||||
|
leal 0x1000+BF_FRAME(ctx),tmp1
|
||||||
|
movl tmp5,BF_ptr
|
||||||
|
cmpl tmp5,tmp1
|
||||||
|
movl P(0),tmp2
|
||||||
|
movl L,-8(tmp5)
|
||||||
|
movl R,-4(tmp5)
|
||||||
|
ja BF_loop_S
|
||||||
|
movl 4(%esp),%esp
|
||||||
|
popl %edi
|
||||||
|
popl %esi
|
||||||
|
popl %ebx
|
||||||
|
popl %ebp
|
||||||
|
ret
|
||||||
|
|
||||||
|
BF_die:
|
||||||
|
/* Oops, need to re-compile with a larger BF_FRAME. */
|
||||||
|
hlt
|
||||||
|
jmp BF_die
|
||||||
|
|
||||||
|
#if defined(__ELF__) && defined(__linux__)
|
||||||
|
.section .note.GNU-stack,"",@progbits
|
||||||
|
#endif
|
||||||
|
#endif
|
|
@ -0,0 +1,41 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 15
|
||||||
|
VisualStudioVersion = 15.0.28307.136
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libbcrypt", "libbcrypt.vcxproj", "{D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}"
|
||||||
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "..\test\test.vcxproj", "{C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x86.ActiveCfg = Debug|Win32
|
||||||
|
{D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x86.Build.0 = Debug|Win32
|
||||||
|
{D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x64.Build.0 = Release|x64
|
||||||
|
{D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x86.ActiveCfg = Release|Win32
|
||||||
|
{D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x86.Build.0 = Release|Win32
|
||||||
|
{C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x86.ActiveCfg = Debug|Win32
|
||||||
|
{C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x86.Build.0 = Debug|Win32
|
||||||
|
{C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x64.Build.0 = Release|x64
|
||||||
|
{C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x86.ActiveCfg = Release|Win32
|
||||||
|
{C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x86.Build.0 = Release|Win32
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {2DB786F9-9679-4A72-A4A0-2544E42B78CB}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
|
@ -0,0 +1,135 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
|
<ProjectConfiguration Include="Debug|Win32">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|Win32">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Debug|x64">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|x64">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<VCProjectVersion>15.0</VCProjectVersion>
|
||||||
|
<ProjectGuid>{D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}</ProjectGuid>
|
||||||
|
<RootNamespace>libbcrypt</RootNamespace>
|
||||||
|
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v141</PlatformToolset>
|
||||||
|
<CharacterSet>MultiByte</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v141</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>MultiByte</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v141</PlatformToolset>
|
||||||
|
<CharacterSet>MultiByte</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v141</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>MultiByte</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<ImportGroup Label="ExtensionSettings">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="Shared">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
<PropertyGroup />
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<Optimization>Disabled</Optimization>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<Optimization>Disabled</Optimization>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<Optimization>MaxSpeed</Optimization>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<Optimization>MaxSpeed</Optimization>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="..\..\src\bcrypt.c" />
|
||||||
|
<ClCompile Include="..\..\src\crypt_blowfish.c" />
|
||||||
|
<ClCompile Include="..\..\src\crypt_gensalt.c" />
|
||||||
|
<ClCompile Include="..\..\src\wrapper.c" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="..\..\include\bcrypt\bcrypt.h" />
|
||||||
|
<ClInclude Include="..\..\include\bcrypt\BCrypt.hpp" />
|
||||||
|
<ClInclude Include="..\..\include\bcrypt\crypt.h" />
|
||||||
|
<ClInclude Include="..\..\include\bcrypt\crypt_blowfish.h" />
|
||||||
|
<ClInclude Include="..\..\include\bcrypt\crypt_gensalt.h" />
|
||||||
|
<ClInclude Include="..\..\include\bcrypt\ow-crypt.h" />
|
||||||
|
<ClInclude Include="..\..\include\bcrypt\winbcrypt.h" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
</ImportGroup>
|
||||||
|
</Project>
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup>
|
||||||
|
<Filter Include="Source Files">
|
||||||
|
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||||
|
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Header Files">
|
||||||
|
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||||
|
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Resource Files">
|
||||||
|
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||||
|
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||||
|
</Filter>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="..\..\src\bcrypt.c">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\src\crypt_blowfish.c">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\src\crypt_gensalt.c">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\src\wrapper.c">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="..\..\include\bcrypt\BCrypt.hpp">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\include\bcrypt\winbcrypt.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\include\bcrypt\bcrypt.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\include\bcrypt\crypt.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\include\bcrypt\crypt_blowfish.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\include\bcrypt\crypt_gensalt.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\include\bcrypt\ow-crypt.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
|
@ -0,0 +1,22 @@
|
||||||
|
#include "../../include/bcrypt/BCrypt.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
string right_password = "right_password";
|
||||||
|
string wrong_password = "wrong_password";
|
||||||
|
|
||||||
|
cout << "generate hash... " << flush;
|
||||||
|
string hash = BCrypt::generateHash(right_password, 12);
|
||||||
|
cout << "done." << endl;
|
||||||
|
|
||||||
|
cout << "checking right password: " << flush
|
||||||
|
<< BCrypt::validatePassword(right_password, hash) << endl;
|
||||||
|
|
||||||
|
cout << "checking wrong password: " << flush
|
||||||
|
<< BCrypt::validatePassword(wrong_password, hash) << endl;
|
||||||
|
|
||||||
|
system("pause");
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
|
<ProjectConfiguration Include="Debug|Win32">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|Win32">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Debug|x64">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|x64">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="main.cpp" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\libbcrypt\libbcrypt.vcxproj">
|
||||||
|
<Project>{d6a9a3f3-1312-4494-85b8-7ce7dd4d78f4}</Project>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<VCProjectVersion>15.0</VCProjectVersion>
|
||||||
|
<ProjectGuid>{C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}</ProjectGuid>
|
||||||
|
<RootNamespace>test</RootNamespace>
|
||||||
|
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v141</PlatformToolset>
|
||||||
|
<CharacterSet>MultiByte</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v141</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>MultiByte</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v141</PlatformToolset>
|
||||||
|
<CharacterSet>MultiByte</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v141</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>MultiByte</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<ImportGroup Label="ExtensionSettings">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="Shared">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
<PropertyGroup />
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<Optimization>Disabled</Optimization>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<AdditionalDependencies>../libbcrypt/Debug/libbcrypt.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<Optimization>Disabled</Optimization>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<Optimization>MaxSpeed</Optimization>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<Optimization>MaxSpeed</Optimization>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
</ImportGroup>
|
||||||
|
</Project>
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup>
|
||||||
|
<Filter Include="Source Files">
|
||||||
|
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||||
|
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Header Files">
|
||||||
|
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||||
|
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Resource Files">
|
||||||
|
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||||
|
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||||
|
</Filter>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="main.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
|
@ -46,7 +46,7 @@ protected:
|
||||||
unsigned int colours;
|
unsigned int colours;
|
||||||
unsigned int subpixelorder;
|
unsigned int subpixelorder;
|
||||||
unsigned int pixels;
|
unsigned int pixels;
|
||||||
unsigned int imagesize;
|
unsigned long long imagesize;
|
||||||
int brightness;
|
int brightness;
|
||||||
int hue;
|
int hue;
|
||||||
int colour;
|
int colour;
|
||||||
|
@ -92,7 +92,7 @@ public:
|
||||||
unsigned int Colours() const { return colours; }
|
unsigned int Colours() const { return colours; }
|
||||||
unsigned int SubpixelOrder() const { return subpixelorder; }
|
unsigned int SubpixelOrder() const { return subpixelorder; }
|
||||||
unsigned int Pixels() const { return pixels; }
|
unsigned int Pixels() const { return pixels; }
|
||||||
unsigned int ImageSize() const { return imagesize; }
|
unsigned long long ImageSize() const { return imagesize; }
|
||||||
unsigned int Bytes() const { return bytes; };
|
unsigned int Bytes() const { return bytes; };
|
||||||
|
|
||||||
virtual int Brightness( int/*p_brightness*/=-1 ) { return -1; }
|
virtual int Brightness( int/*p_brightness*/=-1 ) { return -1; }
|
||||||
|
|
|
@ -69,7 +69,7 @@ void zmLoadConfig() {
|
||||||
if ( ! staticConfig.SERVER_NAME.empty() ) {
|
if ( ! staticConfig.SERVER_NAME.empty() ) {
|
||||||
|
|
||||||
Debug( 1, "Fetching ZM_SERVER_ID For Name = %s", staticConfig.SERVER_NAME.c_str() );
|
Debug( 1, "Fetching ZM_SERVER_ID For Name = %s", staticConfig.SERVER_NAME.c_str() );
|
||||||
std::string sql = stringtf("SELECT Id FROM Servers WHERE Name='%s'", staticConfig.SERVER_NAME.c_str() );
|
std::string sql = stringtf("SELECT `Id` FROM `Servers` WHERE `Name`='%s'", staticConfig.SERVER_NAME.c_str() );
|
||||||
zmDbRow dbrow;
|
zmDbRow dbrow;
|
||||||
if ( dbrow.fetch( sql.c_str() ) ) {
|
if ( dbrow.fetch( sql.c_str() ) ) {
|
||||||
staticConfig.SERVER_ID = atoi(dbrow[0]);
|
staticConfig.SERVER_ID = atoi(dbrow[0]);
|
||||||
|
@ -80,7 +80,7 @@ void zmLoadConfig() {
|
||||||
} // end if has SERVER_NAME
|
} // end if has SERVER_NAME
|
||||||
} else if ( staticConfig.SERVER_NAME.empty() ) {
|
} else if ( staticConfig.SERVER_NAME.empty() ) {
|
||||||
Debug( 1, "Fetching ZM_SERVER_NAME For Id = %d", staticConfig.SERVER_ID );
|
Debug( 1, "Fetching ZM_SERVER_NAME For Id = %d", staticConfig.SERVER_ID );
|
||||||
std::string sql = stringtf("SELECT Name FROM Servers WHERE Id='%d'", staticConfig.SERVER_ID );
|
std::string sql = stringtf("SELECT `Name` FROM `Servers` WHERE `Id`='%d'", staticConfig.SERVER_ID );
|
||||||
|
|
||||||
zmDbRow dbrow;
|
zmDbRow dbrow;
|
||||||
if ( dbrow.fetch( sql.c_str() ) ) {
|
if ( dbrow.fetch( sql.c_str() ) ) {
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
#include "zm.h"
|
||||||
|
# include "zm_crypt.h"
|
||||||
|
#include "BCrypt.hpp"
|
||||||
|
#include "jwt.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
|
// returns username if valid, "" if not
|
||||||
|
std::pair <std::string, unsigned int> verifyToken(std::string jwt_token_str, std::string key) {
|
||||||
|
std::string username = "";
|
||||||
|
unsigned int token_issued_at = 0;
|
||||||
|
try {
|
||||||
|
// is it decodable?
|
||||||
|
auto decoded = jwt::decode(jwt_token_str);
|
||||||
|
auto verifier = jwt::verify()
|
||||||
|
.allow_algorithm(jwt::algorithm::hs256{ key })
|
||||||
|
.with_issuer("ZoneMinder");
|
||||||
|
|
||||||
|
// signature verified?
|
||||||
|
verifier.verify(decoded);
|
||||||
|
|
||||||
|
// make sure it has fields we need
|
||||||
|
if (decoded.has_payload_claim("type")) {
|
||||||
|
std::string type = decoded.get_payload_claim("type").as_string();
|
||||||
|
if (type != "access") {
|
||||||
|
Error ("Only access tokens are allowed. Please do not use refresh tokens");
|
||||||
|
return std::make_pair("",0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// something is wrong. All ZM tokens have type
|
||||||
|
Error ("Missing token type. This should not happen");
|
||||||
|
return std::make_pair("",0);
|
||||||
|
}
|
||||||
|
if (decoded.has_payload_claim("user")) {
|
||||||
|
username = decoded.get_payload_claim("user").as_string();
|
||||||
|
Debug (1, "Got %s as user claim from token", username.c_str());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Error ("User not found in claim");
|
||||||
|
return std::make_pair("",0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decoded.has_payload_claim("iat")) {
|
||||||
|
token_issued_at = (unsigned int) (decoded.get_payload_claim("iat").as_int());
|
||||||
|
Debug (1,"Got IAT token=%u", token_issued_at);
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Error ("IAT not found in claim. This should not happen");
|
||||||
|
return std::make_pair("",0);
|
||||||
|
}
|
||||||
|
} // try
|
||||||
|
catch (const std::exception &e) {
|
||||||
|
Error("Unable to verify token: %s", e.what());
|
||||||
|
return std::make_pair("",0);
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
Error ("unknown exception");
|
||||||
|
return std::make_pair("",0);
|
||||||
|
|
||||||
|
}
|
||||||
|
return std::make_pair(username,token_issued_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool verifyPassword(const char *username, const char *input_password, const char *db_password_hash) {
|
||||||
|
bool password_correct = false;
|
||||||
|
if ( strlen(db_password_hash) < 4 ) {
|
||||||
|
// actually, shoud be more, but this is min. for next code
|
||||||
|
Error("DB Password is too short or invalid to check");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ( db_password_hash[0] == '*' ) {
|
||||||
|
// MYSQL PASSWORD
|
||||||
|
Debug(1, "%s is using an MD5 encoded password", username);
|
||||||
|
|
||||||
|
SHA_CTX ctx1, ctx2;
|
||||||
|
unsigned char digest_interim[SHA_DIGEST_LENGTH];
|
||||||
|
unsigned char digest_final[SHA_DIGEST_LENGTH];
|
||||||
|
|
||||||
|
//get first iteration
|
||||||
|
SHA1_Init(&ctx1);
|
||||||
|
SHA1_Update(&ctx1, input_password, strlen(input_password));
|
||||||
|
SHA1_Final(digest_interim, &ctx1);
|
||||||
|
|
||||||
|
//2nd iteration
|
||||||
|
SHA1_Init(&ctx2);
|
||||||
|
SHA1_Update(&ctx2, digest_interim,SHA_DIGEST_LENGTH);
|
||||||
|
SHA1_Final (digest_final, &ctx2);
|
||||||
|
|
||||||
|
char final_hash[SHA_DIGEST_LENGTH * 2 +2];
|
||||||
|
final_hash[0] = '*';
|
||||||
|
//convert to hex
|
||||||
|
for ( int i = 0; i < SHA_DIGEST_LENGTH; i++ )
|
||||||
|
sprintf(&final_hash[i*2]+1, "%02X", (unsigned int)digest_final[i]);
|
||||||
|
final_hash[SHA_DIGEST_LENGTH *2 + 1] = 0;
|
||||||
|
|
||||||
|
Debug(1, "Computed password_hash:%s, stored password_hash:%s", final_hash, db_password_hash);
|
||||||
|
password_correct = (strcmp(db_password_hash, final_hash)==0);
|
||||||
|
} else if (
|
||||||
|
(db_password_hash[0] == '$')
|
||||||
|
&&
|
||||||
|
(db_password_hash[1]== '2')
|
||||||
|
&&
|
||||||
|
(db_password_hash[3] == '$')
|
||||||
|
) {
|
||||||
|
// BCRYPT
|
||||||
|
Debug(1, "%s is using a bcrypt encoded password", username);
|
||||||
|
BCrypt bcrypt;
|
||||||
|
std::string input_hash = bcrypt.generateHash(std::string(input_password));
|
||||||
|
password_correct = bcrypt.validatePassword(std::string(input_password), std::string(db_password_hash));
|
||||||
|
} else if ( strncmp(db_password_hash, "-ZM-",4) == 0 ) {
|
||||||
|
Error("Authentication failed - migration of password not complete. Please log into web console for this user and retry this operation");
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
Warning("%s is using a plain text (not recommended) or scheme not understood", username);
|
||||||
|
password_correct = (strcmp(input_password, db_password_hash) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return password_correct;
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
//
|
||||||
|
// ZoneMinder General Utility Functions, $Date$, $Revision$
|
||||||
|
// Copyright (C) 2001-2008 Philip Coombes
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// of the License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef ZM_CRYPT_H
|
||||||
|
#define ZM_CRYPT_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool verifyPassword( const char *username, const char *input_password, const char *db_password_hash);
|
||||||
|
|
||||||
|
std::pair <std::string, unsigned int> verifyToken(std::string token, std::string key);
|
||||||
|
#endif // ZM_CRYPT_H
|
|
@ -88,8 +88,8 @@ Event::Event(
|
||||||
|
|
||||||
char sql[ZM_SQL_MED_BUFSIZ];
|
char sql[ZM_SQL_MED_BUFSIZ];
|
||||||
snprintf(sql, sizeof(sql),
|
snprintf(sql, sizeof(sql),
|
||||||
"INSERT INTO Events "
|
"INSERT INTO `Events` "
|
||||||
"( MonitorId, StorageId, Name, StartTime, Width, Height, Cause, Notes, StateId, Orientation, Videoed, DefaultVideo, SaveJPEGs, Scheme )"
|
"( `MonitorId`, `StorageId`, `Name`, `StartTime`, `Width`, `Height`, `Cause`, `Notes`, `StateId`, `Orientation`, `Videoed`, `DefaultVideo`, `SaveJPEGs`, `Scheme` )"
|
||||||
" VALUES "
|
" VALUES "
|
||||||
"( %d, %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', %d, %d, %d, '', %d, '%s' )",
|
"( %d, %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', %d, %d, %d, '', %d, '%s' )",
|
||||||
monitor->Id(),
|
monitor->Id(),
|
||||||
|
@ -250,6 +250,7 @@ Event::~Event() {
|
||||||
Debug(2, "start_time:%d.%d end_time%d.%d", start_time.tv_sec, start_time.tv_usec, end_time.tv_sec, end_time.tv_usec);
|
Debug(2, "start_time:%d.%d end_time%d.%d", start_time.tv_sec, start_time.tv_usec, end_time.tv_sec, end_time.tv_usec);
|
||||||
|
|
||||||
if ( frames > last_db_frame ) {
|
if ( frames > last_db_frame ) {
|
||||||
|
frames ++;
|
||||||
Debug(1, "Adding closing frame %d to DB", frames);
|
Debug(1, "Adding closing frame %d to DB", frames);
|
||||||
frame_data.push(new Frame(id, frames, NORMAL, end_time, delta_time, 0));
|
frame_data.push(new Frame(id, frames, NORMAL, end_time, delta_time, 0));
|
||||||
}
|
}
|
||||||
|
@ -546,6 +547,8 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a
|
||||||
bool write_to_db = false;
|
bool write_to_db = false;
|
||||||
|
|
||||||
if ( save_jpegs & 1 ) {
|
if ( save_jpegs & 1 ) {
|
||||||
|
if ( frames == 1 )
|
||||||
|
write_to_db = true; // web ui might show this as thumbnail, so db needs to know about it.
|
||||||
static char event_file[PATH_MAX];
|
static char event_file[PATH_MAX];
|
||||||
snprintf(event_file, sizeof(event_file), staticConfig.capture_file_format, path, frames);
|
snprintf(event_file, sizeof(event_file), staticConfig.capture_file_format, path, frames);
|
||||||
Debug(1, "Writing capture frame %d to %s", frames, event_file);
|
Debug(1, "Writing capture frame %d to %s", frames, event_file);
|
||||||
|
@ -574,22 +577,22 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a
|
||||||
if ( score < 0 )
|
if ( score < 0 )
|
||||||
score = 0;
|
score = 0;
|
||||||
|
|
||||||
bool db_frame = ( frame_type != BULK ) || (!frames) || ((frames%config.bulk_frame_interval)==0) ;
|
bool db_frame = ( frame_type != BULK ) || (frames==1) || ((frames%config.bulk_frame_interval)==0) ;
|
||||||
if ( db_frame ) {
|
if ( db_frame ) {
|
||||||
|
|
||||||
static char sql[ZM_SQL_MED_BUFSIZ];
|
static char sql[ZM_SQL_MED_BUFSIZ];
|
||||||
|
|
||||||
frame_data.push(new Frame(id, frames, frame_type, timestamp, delta_time, score));
|
frame_data.push(new Frame(id, frames, frame_type, timestamp, delta_time, score));
|
||||||
if ( write_to_db || ( frame_data.size() > 20 ) ) {
|
if ( write_to_db || ( frame_data.size() > 20 ) ) {
|
||||||
|
Debug(1, "Adding %d frames to DB", frame_data.size());
|
||||||
WriteDbFrames();
|
WriteDbFrames();
|
||||||
Debug(1, "Adding 20 frames to DB");
|
|
||||||
last_db_frame = frames;
|
last_db_frame = frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We are writing a Bulk frame
|
// We are writing a Bulk frame
|
||||||
if ( frame_type == BULK ) {
|
if ( frame_type == BULK ) {
|
||||||
snprintf(sql, sizeof(sql),
|
snprintf(sql, sizeof(sql),
|
||||||
"UPDATE Events SET Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d where Id = %" PRIu64,
|
"UPDATE Events SET Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64,
|
||||||
( delta_time.positive?"":"-" ),
|
( delta_time.positive?"":"-" ),
|
||||||
delta_time.sec, delta_time.fsec,
|
delta_time.sec, delta_time.fsec,
|
||||||
frames,
|
frames,
|
||||||
|
|
|
@ -41,10 +41,12 @@
|
||||||
|
|
||||||
#include "zm_sendfile.h"
|
#include "zm_sendfile.h"
|
||||||
|
|
||||||
bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) {
|
bool EventStream::loadInitialEventData(int monitor_id, time_t event_time) {
|
||||||
static char sql[ZM_SQL_SML_BUFSIZ];
|
static char sql[ZM_SQL_SML_BUFSIZ];
|
||||||
|
|
||||||
snprintf(sql, sizeof(sql), "SELECT Id FROM Events WHERE MonitorId = %d AND unix_timestamp(EndTime) > %ld ORDER BY Id ASC LIMIT 1", monitor_id, event_time);
|
snprintf(sql, sizeof(sql), "SELECT `Id` FROM `Events` WHERE "
|
||||||
|
"`MonitorId` = %d AND unix_timestamp(`EndTime`) > %ld "
|
||||||
|
"ORDER BY `Id` ASC LIMIT 1", monitor_id, event_time);
|
||||||
|
|
||||||
if ( mysql_query(&dbconn, sql) ) {
|
if ( mysql_query(&dbconn, sql) ) {
|
||||||
Error("Can't run query: %s", mysql_error(&dbconn));
|
Error("Can't run query: %s", mysql_error(&dbconn));
|
||||||
|
@ -81,14 +83,17 @@ bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) {
|
||||||
Debug(3, "Set curr_stream_time:%.2f, curr_frame_id:%d", curr_stream_time, curr_frame_id);
|
Debug(3, "Set curr_stream_time:%.2f, curr_frame_id:%d", curr_stream_time, curr_frame_id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
} // end foreach frame
|
||||||
Debug(3, "Skipping %ld frames", event_data->frame_count);
|
Debug(3, "Skipping %ld frames", event_data->frame_count);
|
||||||
|
} else {
|
||||||
|
Warning("Requested an event time less than the start of the event. event_time %.2f < start_time %.2f",
|
||||||
|
event_time, event_data->start_time);
|
||||||
}
|
}
|
||||||
}
|
} // end if have a start time
|
||||||
return true;
|
return true;
|
||||||
}
|
} // bool EventStream::loadInitialEventData( int monitor_id, time_t event_time )
|
||||||
|
|
||||||
bool EventStream::loadInitialEventData( uint64_t init_event_id, unsigned int init_frame_id ) {
|
bool EventStream::loadInitialEventData(uint64_t init_event_id, unsigned int init_frame_id) {
|
||||||
loadEventData(init_event_id);
|
loadEventData(init_event_id);
|
||||||
|
|
||||||
if ( init_frame_id ) {
|
if ( init_frame_id ) {
|
||||||
|
@ -110,9 +115,9 @@ bool EventStream::loadEventData(uint64_t event_id) {
|
||||||
static char sql[ZM_SQL_MED_BUFSIZ];
|
static char sql[ZM_SQL_MED_BUFSIZ];
|
||||||
|
|
||||||
snprintf(sql, sizeof(sql),
|
snprintf(sql, sizeof(sql),
|
||||||
"SELECT MonitorId, StorageId, Frames, unix_timestamp( StartTime ) AS StartTimestamp, "
|
"SELECT `MonitorId`, `StorageId`, `Frames`, unix_timestamp( `StartTime` ) AS StartTimestamp, "
|
||||||
"(SELECT max(Delta)-min(Delta) FROM Frames WHERE EventId=Events.Id) AS Duration, "
|
"(SELECT max(`Delta`)-min(`Delta`) FROM `Frames` WHERE `EventId`=`Events.Id`) AS Duration, "
|
||||||
"DefaultVideo, Scheme, SaveJPEGs FROM Events WHERE Id = %" PRIu64, event_id);
|
"`DefaultVideo`, `Scheme`, `SaveJPEGs` FROM `Events` WHERE `Id` = %" PRIu64, event_id);
|
||||||
|
|
||||||
if ( mysql_query(&dbconn, sql) ) {
|
if ( mysql_query(&dbconn, sql) ) {
|
||||||
Error("Can't run query: %s", mysql_error(&dbconn));
|
Error("Can't run query: %s", mysql_error(&dbconn));
|
||||||
|
@ -155,7 +160,7 @@ bool EventStream::loadEventData(uint64_t event_id) {
|
||||||
event_data->scheme = Storage::SHALLOW;
|
event_data->scheme = Storage::SHALLOW;
|
||||||
}
|
}
|
||||||
event_data->SaveJPEGs = dbrow[7] == NULL ? 0 : atoi(dbrow[7]);
|
event_data->SaveJPEGs = dbrow[7] == NULL ? 0 : atoi(dbrow[7]);
|
||||||
mysql_free_result( result );
|
mysql_free_result(result);
|
||||||
|
|
||||||
Storage * storage = new Storage(event_data->storage_id);
|
Storage * storage = new Storage(event_data->storage_id);
|
||||||
const char *storage_path = storage->Path();
|
const char *storage_path = storage->Path();
|
||||||
|
@ -202,9 +207,11 @@ bool EventStream::loadEventData(uint64_t event_id) {
|
||||||
delete storage; storage = NULL;
|
delete storage; storage = NULL;
|
||||||
|
|
||||||
updateFrameRate((double)event_data->frame_count/event_data->duration);
|
updateFrameRate((double)event_data->frame_count/event_data->duration);
|
||||||
Debug(3,"fps set by frame_count(%d)/duration(%f)", event_data->frame_count, event_data->duration);
|
Debug(3, "fps set by frame_count(%d)/duration(%f)",
|
||||||
|
event_data->frame_count, event_data->duration);
|
||||||
|
|
||||||
snprintf(sql, sizeof(sql), "SELECT FrameId, unix_timestamp(`TimeStamp`), Delta FROM Frames WHERE EventId = %" PRIu64 " ORDER BY FrameId ASC", event_id);
|
snprintf(sql, sizeof(sql), "SELECT `FrameId`, unix_timestamp(`TimeStamp`), `Delta` "
|
||||||
|
"FROM `Frames` WHERE `EventId` = %" PRIu64 " ORDER BY `FrameId` ASC", event_id);
|
||||||
if ( mysql_query(&dbconn, sql) ) {
|
if ( mysql_query(&dbconn, sql) ) {
|
||||||
Error("Can't run query: %s", mysql_error(&dbconn));
|
Error("Can't run query: %s", mysql_error(&dbconn));
|
||||||
exit(mysql_errno(&dbconn));
|
exit(mysql_errno(&dbconn));
|
||||||
|
@ -223,7 +230,7 @@ bool EventStream::loadEventData(uint64_t event_id) {
|
||||||
double last_timestamp = event_data->start_time;
|
double last_timestamp = event_data->start_time;
|
||||||
double last_delta = 0.0;
|
double last_delta = 0.0;
|
||||||
|
|
||||||
while ( ( dbrow = mysql_fetch_row( result ) ) ) {
|
while ( ( dbrow = mysql_fetch_row(result) ) ) {
|
||||||
int id = atoi(dbrow[0]);
|
int id = atoi(dbrow[0]);
|
||||||
//timestamp = atof(dbrow[1]);
|
//timestamp = atof(dbrow[1]);
|
||||||
double delta = atof(dbrow[2]);
|
double delta = atof(dbrow[2]);
|
||||||
|
@ -237,13 +244,13 @@ bool EventStream::loadEventData(uint64_t event_id) {
|
||||||
event_data->frames[i-1].timestamp = last_timestamp + ((i-last_id)*frame_delta);
|
event_data->frames[i-1].timestamp = last_timestamp + ((i-last_id)*frame_delta);
|
||||||
event_data->frames[i-1].offset = event_data->frames[i-1].timestamp - event_data->start_time;
|
event_data->frames[i-1].offset = event_data->frames[i-1].timestamp - event_data->start_time;
|
||||||
event_data->frames[i-1].in_db = false;
|
event_data->frames[i-1].in_db = false;
|
||||||
Debug(3,"Frame %d timestamp:(%f), offset(%f) delta(%f), in_db(%d)",
|
Debug(3,"Frame %d timestamp:(%f), offset(%f) delta(%f), in_db(%d)",
|
||||||
i,
|
i,
|
||||||
event_data->frames[i-1].timestamp,
|
event_data->frames[i-1].timestamp,
|
||||||
event_data->frames[i-1].offset,
|
event_data->frames[i-1].offset,
|
||||||
event_data->frames[i-1].delta,
|
event_data->frames[i-1].delta,
|
||||||
event_data->frames[i-1].in_db
|
event_data->frames[i-1].in_db
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
event_data->frames[id-1].timestamp = event_data->start_time + delta;
|
event_data->frames[id-1].timestamp = event_data->start_time + delta;
|
||||||
|
@ -253,7 +260,7 @@ bool EventStream::loadEventData(uint64_t event_id) {
|
||||||
last_id = id;
|
last_id = id;
|
||||||
last_delta = delta;
|
last_delta = delta;
|
||||||
last_timestamp = event_data->frames[id-1].timestamp;
|
last_timestamp = event_data->frames[id-1].timestamp;
|
||||||
Debug(4,"Frame %d timestamp:(%f), offset(%f) delta(%f), in_db(%d)",
|
Debug(4, "Frame %d timestamp:(%f), offset(%f) delta(%f), in_db(%d)",
|
||||||
id,
|
id,
|
||||||
event_data->frames[id-1].timestamp,
|
event_data->frames[id-1].timestamp,
|
||||||
event_data->frames[id-1].offset,
|
event_data->frames[id-1].offset,
|
||||||
|
@ -261,24 +268,21 @@ bool EventStream::loadEventData(uint64_t event_id) {
|
||||||
event_data->frames[id-1].in_db
|
event_data->frames[id-1].in_db
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if ( mysql_errno( &dbconn ) ) {
|
if ( mysql_errno(&dbconn) ) {
|
||||||
Error("Can't fetch row: %s", mysql_error(&dbconn));
|
Error("Can't fetch row: %s", mysql_error(&dbconn));
|
||||||
exit(mysql_errno(&dbconn));
|
exit(mysql_errno(&dbconn));
|
||||||
}
|
}
|
||||||
|
|
||||||
mysql_free_result(result);
|
mysql_free_result(result);
|
||||||
//for ( int i = 0; i < 250; i++ )
|
|
||||||
//{
|
|
||||||
//Info( "%d -> %d @ %f (%d)", i+1, event_data->frames[i].timestamp, event_data->frames[i].delta, event_data->frames[i].in_db );
|
|
||||||
//}
|
|
||||||
|
|
||||||
if ( event_data->video_file[0] ) {
|
if ( event_data->video_file[0] ) {
|
||||||
char filepath[PATH_MAX];
|
std::string filepath = std::string(event_data->path) + "/" + std::string(event_data->video_file);
|
||||||
snprintf(filepath, sizeof(filepath), "%s/%s", event_data->path, event_data->video_file);
|
//char filepath[PATH_MAX];
|
||||||
Debug(1, "Loading video file from %s", filepath);
|
//snprintf(filepath, sizeof(filepath), "%s/%s", event_data->path, event_data->video_file);
|
||||||
|
Debug(1, "Loading video file from %s", filepath.c_str());
|
||||||
ffmpeg_input = new FFmpeg_Input();
|
ffmpeg_input = new FFmpeg_Input();
|
||||||
if ( 0 > ffmpeg_input->Open( filepath ) ) {
|
if ( 0 > ffmpeg_input->Open(filepath.c_str()) ) {
|
||||||
Warning("Unable to open ffmpeg_input %s/%s", event_data->path, event_data->video_file);
|
Warning("Unable to open ffmpeg_input %s", filepath.c_str());
|
||||||
delete ffmpeg_input;
|
delete ffmpeg_input;
|
||||||
ffmpeg_input = NULL;
|
ffmpeg_input = NULL;
|
||||||
}
|
}
|
||||||
|
@ -290,7 +294,8 @@ bool EventStream::loadEventData(uint64_t event_id) {
|
||||||
else
|
else
|
||||||
curr_stream_time = event_data->frames[event_data->frame_count-1].timestamp;
|
curr_stream_time = event_data->frames[event_data->frame_count-1].timestamp;
|
||||||
}
|
}
|
||||||
Debug(2, "Event:%" PRIu64 ", Frames:%ld, Duration: %.2f", event_data->event_id, event_data->frame_count, event_data->duration);
|
Debug(2, "Event:%" PRIu64 ", Frames:%ld, Duration: %.2f",
|
||||||
|
event_data->event_id, event_data->frame_count, event_data->duration);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} // bool EventStream::loadEventData( int event_id )
|
} // bool EventStream::loadEventData( int event_id )
|
||||||
|
@ -314,11 +319,17 @@ void EventStream::processCommand(const CmdMsg *msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are in single event mode and at the last frame, replay the current event
|
// If we are in single event mode and at the last frame, replay the current event
|
||||||
if ( (mode == MODE_SINGLE || mode == MODE_NONE) && ((unsigned int)curr_frame_id == event_data->frame_count) ) {
|
if (
|
||||||
|
(mode == MODE_SINGLE || mode == MODE_NONE)
|
||||||
|
&&
|
||||||
|
((unsigned int)curr_frame_id == event_data->frame_count)
|
||||||
|
) {
|
||||||
Debug(1, "Was in single or no replay mode, and at last frame, so jumping to 1st frame");
|
Debug(1, "Was in single or no replay mode, and at last frame, so jumping to 1st frame");
|
||||||
curr_frame_id = 1;
|
curr_frame_id = 1;
|
||||||
} else {
|
} else {
|
||||||
Debug(1, "mode is %s, current frame is %d, frame count is %d", (mode == MODE_SINGLE ? "single" : "not single" ), curr_frame_id, event_data->frame_count );
|
Debug(1, "mode is %s, current frame is %d, frame count is %d",
|
||||||
|
(mode == MODE_SINGLE ? "single" : "not single"),
|
||||||
|
curr_frame_id, event_data->frame_count );
|
||||||
}
|
}
|
||||||
|
|
||||||
replay_rate = ZM_RATE_BASE;
|
replay_rate = ZM_RATE_BASE;
|
||||||
|
@ -470,6 +481,7 @@ void EventStream::processCommand(const CmdMsg *msg) {
|
||||||
break;
|
break;
|
||||||
case CMD_SEEK :
|
case CMD_SEEK :
|
||||||
{
|
{
|
||||||
|
// offset is in seconds
|
||||||
int offset = ((unsigned char)msg->msg_data[1]<<24)|((unsigned char)msg->msg_data[2]<<16)|((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4];
|
int offset = ((unsigned char)msg->msg_data[1]<<24)|((unsigned char)msg->msg_data[2]<<16)|((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4];
|
||||||
curr_frame_id = (int)(event_data->frame_count*offset/event_data->duration);
|
curr_frame_id = (int)(event_data->frame_count*offset/event_data->duration);
|
||||||
Debug(1, "Got SEEK command, to %d (new cfid: %d)", offset, curr_frame_id);
|
Debug(1, "Got SEEK command, to %d (new cfid: %d)", offset, curr_frame_id);
|
||||||
|
@ -510,7 +522,7 @@ void EventStream::processCommand(const CmdMsg *msg) {
|
||||||
DataMsg status_msg;
|
DataMsg status_msg;
|
||||||
status_msg.msg_type = MSG_DATA_EVENT;
|
status_msg.msg_type = MSG_DATA_EVENT;
|
||||||
memcpy(&status_msg.msg_data, &status_data, sizeof(status_data));
|
memcpy(&status_msg.msg_data, &status_data, sizeof(status_data));
|
||||||
Debug(1,"Size of msg %d", sizeof(status_data));
|
Debug(1, "Size of msg %d", sizeof(status_data));
|
||||||
if ( sendto(sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr)) < 0 ) {
|
if ( sendto(sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr)) < 0 ) {
|
||||||
//if ( errno != EAGAIN )
|
//if ( errno != EAGAIN )
|
||||||
{
|
{
|
||||||
|
@ -519,7 +531,7 @@ void EventStream::processCommand(const CmdMsg *msg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// quit after sending a status, if this was a quit request
|
// quit after sending a status, if this was a quit request
|
||||||
if ( (MsgCommand)msg->msg_data[0]==CMD_QUIT )
|
if ( (MsgCommand)msg->msg_data[0] == CMD_QUIT )
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|
||||||
updateFrameRate((double)event_data->frame_count/event_data->duration);
|
updateFrameRate((double)event_data->frame_count/event_data->duration);
|
||||||
|
@ -529,17 +541,22 @@ void EventStream::checkEventLoaded() {
|
||||||
static char sql[ZM_SQL_SML_BUFSIZ];
|
static char sql[ZM_SQL_SML_BUFSIZ];
|
||||||
|
|
||||||
if ( curr_frame_id <= 0 ) {
|
if ( curr_frame_id <= 0 ) {
|
||||||
snprintf(sql, sizeof(sql), "SELECT Id FROM Events WHERE MonitorId = %ld AND Id < %" PRIu64 " ORDER BY Id DESC LIMIT 1", event_data->monitor_id, event_data->event_id);
|
snprintf(sql, sizeof(sql),
|
||||||
|
"SELECT `Id` FROM `Events` WHERE `MonitorId` = %ld AND `Id` < %" PRIu64 " ORDER BY `Id` DESC LIMIT 1",
|
||||||
|
event_data->monitor_id, event_data->event_id);
|
||||||
} else if ( (unsigned int)curr_frame_id > event_data->frame_count ) {
|
} else if ( (unsigned int)curr_frame_id > event_data->frame_count ) {
|
||||||
snprintf(sql, sizeof(sql), "SELECT Id FROM Events WHERE MonitorId = %ld AND Id > %" PRIu64 " ORDER BY Id ASC LIMIT 1", event_data->monitor_id, event_data->event_id);
|
snprintf(sql, sizeof(sql),
|
||||||
|
"SELECT `Id` FROM `Events` WHERE `MonitorId` = %ld AND `Id` > %" PRIu64 " ORDER BY `Id` ASC LIMIT 1",
|
||||||
|
event_data->monitor_id, event_data->event_id);
|
||||||
} else {
|
} else {
|
||||||
// No event change required
|
// No event change required
|
||||||
//Debug(3, "No event change required");
|
Debug(3, "No event change required, as curr frame %d <=> event frames %d",
|
||||||
|
curr_frame_id, event_data->frame_count);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event change required.
|
// Event change required.
|
||||||
if ( forceEventChange || ( mode != MODE_SINGLE && mode != MODE_NONE ) ) {
|
if ( forceEventChange || ( (mode != MODE_SINGLE) && (mode != MODE_NONE) ) ) {
|
||||||
if ( mysql_query(&dbconn, sql) ) {
|
if ( mysql_query(&dbconn, sql) ) {
|
||||||
Error("Can't run query: %s", mysql_error(&dbconn));
|
Error("Can't run query: %s", mysql_error(&dbconn));
|
||||||
exit(mysql_errno(&dbconn));
|
exit(mysql_errno(&dbconn));
|
||||||
|
@ -564,21 +581,24 @@ void EventStream::checkEventLoaded() {
|
||||||
loadEventData(event_id);
|
loadEventData(event_id);
|
||||||
|
|
||||||
Debug(2, "Current frame id = %d", curr_frame_id);
|
Debug(2, "Current frame id = %d", curr_frame_id);
|
||||||
if ( replay_rate < 0 ) //rewind
|
if ( replay_rate < 0 ) // rewind
|
||||||
curr_frame_id = event_data->frame_count;
|
curr_frame_id = event_data->frame_count;
|
||||||
else
|
else
|
||||||
curr_frame_id = 1;
|
curr_frame_id = 1;
|
||||||
Debug(2, "New frame id = %d", curr_frame_id);
|
Debug(2, "New frame id = %d", curr_frame_id);
|
||||||
} else {
|
} else {
|
||||||
|
Debug(2, "No next event loaded using %s. Pausing", sql);
|
||||||
if ( curr_frame_id <= 0 )
|
if ( curr_frame_id <= 0 )
|
||||||
curr_frame_id = 1;
|
curr_frame_id = 1;
|
||||||
else
|
else
|
||||||
curr_frame_id = event_data->frame_count;
|
curr_frame_id = event_data->frame_count;
|
||||||
paused = true;
|
paused = true;
|
||||||
|
sendTextFrame("No more event data found");
|
||||||
} // end if found a new event or not
|
} // end if found a new event or not
|
||||||
mysql_free_result(result);
|
mysql_free_result(result);
|
||||||
forceEventChange = false;
|
forceEventChange = false;
|
||||||
} else {
|
} else {
|
||||||
|
Debug(2, "Pausing because mode is %d", mode);
|
||||||
if ( curr_frame_id <= 0 )
|
if ( curr_frame_id <= 0 )
|
||||||
curr_frame_id = 1;
|
curr_frame_id = 1;
|
||||||
else
|
else
|
||||||
|
@ -590,9 +610,8 @@ void EventStream::checkEventLoaded() {
|
||||||
Image * EventStream::getImage( ) {
|
Image * EventStream::getImage( ) {
|
||||||
static char filepath[PATH_MAX];
|
static char filepath[PATH_MAX];
|
||||||
|
|
||||||
Debug(2, "EventStream::getImage path(%s) frame(%d)", event_data->path, curr_frame_id);
|
|
||||||
snprintf(filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id);
|
snprintf(filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id);
|
||||||
Debug(2, "EventStream::getImage path(%s) ", filepath, curr_frame_id);
|
Debug(2, "EventStream::getImage path(%s) from %s frame(%d) ", filepath, event_data->path, curr_frame_id);
|
||||||
Image *image = new Image(filepath);
|
Image *image = new Image(filepath);
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
@ -604,16 +623,16 @@ bool EventStream::sendFrame(int delta_us) {
|
||||||
static struct stat filestat;
|
static struct stat filestat;
|
||||||
FILE *fdj = NULL;
|
FILE *fdj = NULL;
|
||||||
|
|
||||||
// This needs to be abstracted. If we are saving jpgs, then load the capture file. If we are only saving analysis frames, then send that.
|
// This needs to be abstracted. If we are saving jpgs, then load the capture file.
|
||||||
// // This is also wrong, need to have this info stored in the event! FIXME
|
// If we are only saving analysis frames, then send that.
|
||||||
if ( event_data->SaveJPEGs & 1 ) {
|
if ( event_data->SaveJPEGs & 1 ) {
|
||||||
snprintf(filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id);
|
snprintf(filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id);
|
||||||
} else if ( event_data->SaveJPEGs & 2 ) {
|
} else if ( event_data->SaveJPEGs & 2 ) {
|
||||||
snprintf(filepath, sizeof(filepath), staticConfig.analyse_file_format, event_data->path, curr_frame_id);
|
snprintf(filepath, sizeof(filepath), staticConfig.analyse_file_format, event_data->path, curr_frame_id);
|
||||||
if ( stat(filepath, &filestat ) < 0 ) {
|
if ( stat(filepath, &filestat) < 0 ) {
|
||||||
Debug(1, "analyze file %s not found will try to stream from other", filepath);
|
Debug(1, "analyze file %s not found will try to stream from other", filepath);
|
||||||
snprintf(filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id);
|
snprintf(filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id);
|
||||||
if ( stat(filepath, &filestat ) < 0 ) {
|
if ( stat(filepath, &filestat) < 0 ) {
|
||||||
Debug(1, "capture file %s not found either", filepath);
|
Debug(1, "capture file %s not found either", filepath);
|
||||||
filepath[0] = 0;
|
filepath[0] = 0;
|
||||||
}
|
}
|
||||||
|
@ -626,7 +645,6 @@ bool EventStream::sendFrame(int delta_us) {
|
||||||
|
|
||||||
#if HAVE_LIBAVCODEC
|
#if HAVE_LIBAVCODEC
|
||||||
if ( type == STREAM_MPEG ) {
|
if ( type == STREAM_MPEG ) {
|
||||||
Debug(2,"Streaming MPEG");
|
|
||||||
Image image(filepath);
|
Image image(filepath);
|
||||||
|
|
||||||
Image *send_image = prepareImage(&image);
|
Image *send_image = prepareImage(&image);
|
||||||
|
@ -741,7 +759,7 @@ Debug(1, "Loading image");
|
||||||
img_buffer_size = fread( img_buffer, 1, sizeof(temp_img_buffer), fdj );
|
img_buffer_size = fread( img_buffer, 1, sizeof(temp_img_buffer), fdj );
|
||||||
if ( fwrite(img_buffer, img_buffer_size, 1, stdout) != 1 ) {
|
if ( fwrite(img_buffer, img_buffer_size, 1, stdout) != 1 ) {
|
||||||
fclose(fdj); /* Close the file handle */
|
fclose(fdj); /* Close the file handle */
|
||||||
Error("Unable to send raw frame %u: %s",curr_frame_id,strerror(errno));
|
Error("Unable to send raw frame %u: %s", curr_frame_id, strerror(errno));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -749,7 +767,7 @@ Debug(1, "Loading image");
|
||||||
fprintf(stdout, "Content-Length: %d\r\n\r\n", img_buffer_size);
|
fprintf(stdout, "Content-Length: %d\r\n\r\n", img_buffer_size);
|
||||||
if ( fwrite(img_buffer, img_buffer_size, 1, stdout) != 1 ) {
|
if ( fwrite(img_buffer, img_buffer_size, 1, stdout) != 1 ) {
|
||||||
fclose(fdj); /* Close the file handle */
|
fclose(fdj); /* Close the file handle */
|
||||||
Error("Unable to send raw frame %u: %s",curr_frame_id,strerror(errno));
|
Error("Unable to send raw frame %u: %s", curr_frame_id, strerror(errno));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -761,18 +779,17 @@ Debug(1, "Loading image");
|
||||||
Error("Unable to send stream frame: %s", strerror(errno));
|
Error("Unable to send stream frame: %s", strerror(errno));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} // end if send_raw or not
|
} // end if send_raw or not
|
||||||
|
|
||||||
fputs("\r\n\r\n", stdout);
|
fputs("\r\n\r\n", stdout);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
} // end if stream MPEG or other
|
} // end if stream MPEG or other
|
||||||
last_frame_sent = TV_2_FLOAT(now);
|
last_frame_sent = TV_2_FLOAT(now);
|
||||||
return true;
|
return true;
|
||||||
} // bool EventStream::sendFrame( int delta_us )
|
} // bool EventStream::sendFrame( int delta_us )
|
||||||
|
|
||||||
void EventStream::runStream() {
|
void EventStream::runStream() {
|
||||||
openComms();
|
openComms();
|
||||||
Debug(3, "Comms open");
|
|
||||||
|
|
||||||
checkInitialised();
|
checkInitialised();
|
||||||
|
|
||||||
|
@ -788,7 +805,6 @@ void EventStream::runStream() {
|
||||||
updateFrameRate((double)event_data->frame_count/event_data->duration);
|
updateFrameRate((double)event_data->frame_count/event_data->duration);
|
||||||
gettimeofday(&start, NULL);
|
gettimeofday(&start, NULL);
|
||||||
uint64_t start_usec = start.tv_sec * 1000000 + start.tv_usec;
|
uint64_t start_usec = start.tv_sec * 1000000 + start.tv_usec;
|
||||||
uint64_t last_frame_offset = 0;
|
|
||||||
|
|
||||||
while ( !zm_terminate ) {
|
while ( !zm_terminate ) {
|
||||||
gettimeofday(&now, NULL);
|
gettimeofday(&now, NULL);
|
||||||
|
@ -800,9 +816,9 @@ void EventStream::runStream() {
|
||||||
// commands may set send_frame to true
|
// commands may set send_frame to true
|
||||||
while ( checkCommandQueue() && !zm_terminate ) {
|
while ( checkCommandQueue() && !zm_terminate ) {
|
||||||
// The idea is to loop here processing all commands before proceeding.
|
// The idea is to loop here processing all commands before proceeding.
|
||||||
Debug(1, "Have command queue");
|
Debug(1, "Have command queue");
|
||||||
}
|
}
|
||||||
Debug(1, "Done command queue");
|
Debug(2, "Done command queue");
|
||||||
|
|
||||||
// Update modified time of the socket .lock file so that we can tell which ones are stale.
|
// Update modified time of the socket .lock file so that we can tell which ones are stale.
|
||||||
if ( now.tv_sec - last_comm_update.tv_sec > 3600 ) {
|
if ( now.tv_sec - last_comm_update.tv_sec > 3600 ) {
|
||||||
|
@ -810,10 +826,11 @@ void EventStream::runStream() {
|
||||||
last_comm_update = now;
|
last_comm_update = now;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Debug(1, "Not checking command queue");
|
Debug(2, "Not checking command queue");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( step != 0 )
|
//if ( step != 0 )// Adding 0 is cheaper than an if 0
|
||||||
|
// curr_frame_id starts at 1 though, so we might skip the first frame?
|
||||||
curr_frame_id += step;
|
curr_frame_id += step;
|
||||||
|
|
||||||
// Detects when we hit end of event and will load the next event or previous event
|
// Detects when we hit end of event and will load the next event or previous event
|
||||||
|
@ -826,7 +843,10 @@ void EventStream::runStream() {
|
||||||
//Info( "cfid:%d", curr_frame_id );
|
//Info( "cfid:%d", curr_frame_id );
|
||||||
//Info( "fdt:%d", frame_data->timestamp );
|
//Info( "fdt:%d", frame_data->timestamp );
|
||||||
if ( !paused ) {
|
if ( !paused ) {
|
||||||
Debug(3,"Not paused");
|
Debug(3, "Not paused at frame %d", curr_frame_id);
|
||||||
|
|
||||||
|
// This next bit is to determine if we are in the current event time wise
|
||||||
|
// and whether to show an image saying how long until the next event.
|
||||||
bool in_event = true;
|
bool in_event = true;
|
||||||
double time_to_event = 0;
|
double time_to_event = 0;
|
||||||
if ( replay_rate > 0 ) {
|
if ( replay_rate > 0 ) {
|
||||||
|
@ -838,43 +858,58 @@ void EventStream::runStream() {
|
||||||
if ( time_to_event > 0 )
|
if ( time_to_event > 0 )
|
||||||
in_event = false;
|
in_event = false;
|
||||||
}
|
}
|
||||||
|
Debug(1, "replay rate(%d) in_event(%d) time_to_event(%f)=curr_stream_time(%f)-frame timestamp:%f",
|
||||||
|
replay_rate, in_event, time_to_event, curr_stream_time, event_data->frames[event_data->frame_count-1].timestamp);
|
||||||
if ( !in_event ) {
|
if ( !in_event ) {
|
||||||
double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent;
|
double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent;
|
||||||
|
Debug(1, "Ctual delta time = %f = %f - %f", actual_delta_time , TV_2_FLOAT(now) , last_frame_sent);
|
||||||
// > 1 second
|
// > 1 second
|
||||||
if ( actual_delta_time > 1 ) {
|
if ( actual_delta_time > 1 ) {
|
||||||
|
Debug(1, "Sending time to next event frame");
|
||||||
static char frame_text[64];
|
static char frame_text[64];
|
||||||
snprintf(frame_text, sizeof(frame_text), "Time to next event = %d seconds", (int)time_to_event);
|
snprintf(frame_text, sizeof(frame_text), "Time to next event = %d seconds", (int)time_to_event);
|
||||||
if ( !sendTextFrame(frame_text) )
|
if ( !sendTextFrame(frame_text) )
|
||||||
zm_terminate = true;
|
zm_terminate = true;
|
||||||
|
} else {
|
||||||
|
Debug(1, "Not Sending time to next event frame because actual delta time is %f", actual_delta_time);
|
||||||
}
|
}
|
||||||
//else
|
//else
|
||||||
//{
|
//{
|
||||||
Debug(2,"Sleeping because paused");
|
// FIXME ICON But we are not paused. We are somehow still in the event?
|
||||||
|
double sleep_time = (replay_rate>0?1:-1) * ((1.0L * replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000));
|
||||||
|
//double sleep_time = (replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000);
|
||||||
|
//// ZM_RATE_BASE == 100, and 1x replay_rate is 100
|
||||||
|
//double sleep_time = ((replay_rate/ZM_RATE_BASE) * STREAM_PAUSE_WAIT)/1000000;
|
||||||
|
if ( ! sleep_time ) {
|
||||||
|
sleep_time += STREAM_PAUSE_WAIT/1000000;
|
||||||
|
}
|
||||||
|
curr_stream_time += sleep_time;
|
||||||
|
Debug(2, "Sleeping (%dus) because we are not at the next event yet, adding %f", STREAM_PAUSE_WAIT, sleep_time);
|
||||||
usleep(STREAM_PAUSE_WAIT);
|
usleep(STREAM_PAUSE_WAIT);
|
||||||
//curr_stream_time += (replay_rate>0?1:-1) * ((1.0L * replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000));
|
|
||||||
curr_stream_time += (1.0L * replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000);
|
//curr_stream_time += (1.0L * replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000);
|
||||||
//}
|
//}
|
||||||
continue;
|
continue;
|
||||||
} // end if !in_event
|
} // end if !in_event
|
||||||
|
|
||||||
// Figure out if we should send this frame
|
// Figure out if we should send this frame
|
||||||
Debug(3,"cur_frame_id (%d-1) mod frame_mod(%d)",curr_frame_id, frame_mod);
|
Debug(3, "cur_frame_id (%d-1) mod frame_mod(%d)", curr_frame_id, frame_mod);
|
||||||
// If we are streaming and this frame is due to be sent
|
// If we are streaming and this frame is due to be sent
|
||||||
// frame mod defaults to 1 and if we are going faster than max_fps will get multiplied by 2
|
// frame mod defaults to 1 and if we are going faster than max_fps will get multiplied by 2
|
||||||
// so if it is 2, then we send every other frame, if is it 4 then every fourth frame, etc.
|
// so if it is 2, then we send every other frame, if is it 4 then every fourth frame, etc.
|
||||||
if ( (frame_mod == 1) || (((curr_frame_id-1)%frame_mod) == 0) ) {
|
if ( (frame_mod == 1) || (((curr_frame_id-1)%frame_mod) == 0) ) {
|
||||||
delta_us = (unsigned int)(frame_data->delta * 1000000);
|
delta_us = (unsigned int)(frame_data->delta * 1000000);
|
||||||
Debug(3,"frame delta %uus ", delta_us);
|
Debug(3, "frame delta %uus ", delta_us);
|
||||||
// if effective > base we should speed up frame delivery
|
// if effective > base we should speed up frame delivery
|
||||||
delta_us = (unsigned int)((delta_us * base_fps)/effective_fps);
|
delta_us = (unsigned int)((delta_us * base_fps)/effective_fps);
|
||||||
Debug(3,"delta %u = base_fps(%f)/effective fps(%f)", delta_us, base_fps, effective_fps);
|
Debug(3, "delta %u = base_fps(%f)/effective fps(%f)", delta_us, base_fps, effective_fps);
|
||||||
// but must not exceed maxfps
|
// but must not exceed maxfps
|
||||||
delta_us = max(delta_us, 1000000 / maxfps);
|
delta_us = max(delta_us, 1000000 / maxfps);
|
||||||
Debug(3,"delta %u = base_fps(%f)/effective fps(%f) from 30fps", delta_us, base_fps, effective_fps);
|
Debug(3, "delta %u = base_fps(%f)/effective fps(%f) from 30fps", delta_us, base_fps, effective_fps);
|
||||||
send_frame = true;
|
send_frame = true;
|
||||||
}
|
}
|
||||||
} else if ( step != 0 ) {
|
} else if ( step != 0 ) {
|
||||||
Debug(2,"Paused with step");
|
Debug(2, "Paused with step");
|
||||||
// We are paused and are just stepping forward or backward one frame
|
// We are paused and are just stepping forward or backward one frame
|
||||||
step = 0;
|
step = 0;
|
||||||
send_frame = true;
|
send_frame = true;
|
||||||
|
@ -894,8 +929,6 @@ Debug(3,"cur_frame_id (%d-1) mod frame_mod(%d)",curr_frame_id, frame_mod);
|
||||||
//Debug(3,"sending frame");
|
//Debug(3,"sending frame");
|
||||||
if ( !sendFrame(delta_us) )
|
if ( !sendFrame(delta_us) )
|
||||||
zm_terminate = true;
|
zm_terminate = true;
|
||||||
//} else {
|
|
||||||
//Debug(3,"Not sending frame");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
curr_stream_time = frame_data->timestamp;
|
curr_stream_time = frame_data->timestamp;
|
||||||
|
@ -903,16 +936,19 @@ Debug(3,"cur_frame_id (%d-1) mod frame_mod(%d)",curr_frame_id, frame_mod);
|
||||||
if ( !paused ) {
|
if ( !paused ) {
|
||||||
// +/- 1? What if we are skipping frames?
|
// +/- 1? What if we are skipping frames?
|
||||||
curr_frame_id += (replay_rate>0) ? frame_mod : -1*frame_mod;
|
curr_frame_id += (replay_rate>0) ? frame_mod : -1*frame_mod;
|
||||||
|
|
||||||
if ( (mode == MODE_SINGLE) && ((unsigned int)curr_frame_id == event_data->frame_count) ) {
|
|
||||||
Debug(2, "Have mode==MODE_SINGLE and at end of event, looping back to start");
|
|
||||||
curr_frame_id = 1;
|
|
||||||
}
|
|
||||||
frame_data = &event_data->frames[curr_frame_id-1];
|
|
||||||
|
|
||||||
// sending the frame may have taken some time, so reload now
|
// sending the frame may have taken some time, so reload now
|
||||||
gettimeofday(&now, NULL);
|
gettimeofday(&now, NULL);
|
||||||
uint64_t now_usec = (now.tv_sec * 1000000 + now.tv_usec);
|
uint64_t now_usec = (now.tv_sec * 1000000 + now.tv_usec);
|
||||||
|
|
||||||
|
// we incremented by replay_rate, so might have jumped past frame_count
|
||||||
|
if ( (mode == MODE_SINGLE) && ((unsigned int)curr_frame_id >= event_data->frame_count) ) {
|
||||||
|
Debug(2, "Have mode==MODE_SINGLE and at end of event, looping back to start");
|
||||||
|
curr_frame_id = 1;
|
||||||
|
// Have to reset start_usec to now when replaying
|
||||||
|
start_usec = now_usec;
|
||||||
|
}
|
||||||
|
frame_data = &event_data->frames[curr_frame_id-1];
|
||||||
|
|
||||||
// frame_data->delta is the time since last frame as a float in seconds
|
// frame_data->delta is the time since last frame as a float in seconds
|
||||||
// but what if we are skipping frames? We need the distance from the last frame sent
|
// but what if we are skipping frames? We need the distance from the last frame sent
|
||||||
// Also, what about reverse? needs to be absolute value
|
// Also, what about reverse? needs to be absolute value
|
||||||
|
@ -921,21 +957,9 @@ Debug(3,"cur_frame_id (%d-1) mod frame_mod(%d)",curr_frame_id, frame_mod);
|
||||||
// you can calculate the relationship between now and the start
|
// you can calculate the relationship between now and the start
|
||||||
// or calc the relationship from the last frame. I think from the start is better as it self-corrects
|
// or calc the relationship from the last frame. I think from the start is better as it self-corrects
|
||||||
|
|
||||||
if ( last_frame_offset ) {
|
|
||||||
// We assume that we are going forward and the next frame is in the future.
|
|
||||||
delta_us = frame_data->offset * 1000000 - (now_usec-start_usec);
|
|
||||||
// - (now_usec - start_usec);
|
|
||||||
Debug(2, "New delta_us now %" PRIu64 " - start %" PRIu64 " = %d offset %" PRId64 " - elapsed = %dusec",
|
|
||||||
now_usec, start_usec, now_usec-start_usec, frame_data->offset * 1000000, delta_us);
|
|
||||||
} else {
|
|
||||||
Debug(2, "No last frame_offset, no sleep");
|
|
||||||
delta_us = 0;
|
|
||||||
}
|
|
||||||
last_frame_offset = frame_data->offset * 1000000;
|
|
||||||
|
|
||||||
if ( send_frame && type != STREAM_MPEG ) {
|
if ( send_frame && type != STREAM_MPEG ) {
|
||||||
if ( delta_us > 0 ) {
|
if ( delta_us > 0 ) {
|
||||||
Debug( 3, "dUs: %d", delta_us );
|
Debug(3, "dUs: %d", delta_us);
|
||||||
usleep(delta_us);
|
usleep(delta_us);
|
||||||
Debug(3, "Done sleeping: %d usec", delta_us);
|
Debug(3, "Done sleeping: %d usec", delta_us);
|
||||||
}
|
}
|
||||||
|
@ -943,19 +967,20 @@ Debug(3,"cur_frame_id (%d-1) mod frame_mod(%d)",curr_frame_id, frame_mod);
|
||||||
} else {
|
} else {
|
||||||
delta_us = ((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*(replay_rate?abs(replay_rate*2):2)));
|
delta_us = ((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*(replay_rate?abs(replay_rate*2):2)));
|
||||||
|
|
||||||
Debug(2,"Sleeping %d because 1000000 * ZM_RATE_BASE(%d) / ( base_fps (%f), replay_rate(%d)",
|
Debug(2, "Sleeping %d because 1000000 * ZM_RATE_BASE(%d) / ( base_fps (%f), replay_rate(%d)",
|
||||||
(unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))),
|
(unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))),
|
||||||
ZM_RATE_BASE,
|
ZM_RATE_BASE,
|
||||||
(base_fps?base_fps:1),
|
(base_fps?base_fps:1),
|
||||||
(replay_rate?abs(replay_rate*2):200)
|
(replay_rate?abs(replay_rate*2):200)
|
||||||
);
|
);
|
||||||
if ( delta_us > 0 and delta_us < 100000 ) {
|
if ( delta_us > 0 and delta_us < 500000 ) {
|
||||||
usleep((unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))));
|
usleep(delta_us);
|
||||||
} else {
|
} else {
|
||||||
//Error("Not sleeping!");
|
// Never want to sleep for too long, limit to .1s
|
||||||
usleep(100000);
|
Warning("sleeping .5s because delta_us (%d) not good!", delta_us);
|
||||||
|
usleep(500000);
|
||||||
}
|
}
|
||||||
}
|
} // end if !paused
|
||||||
} // end while ! zm_terminate
|
} // end while ! zm_terminate
|
||||||
#if HAVE_LIBAVCODEC
|
#if HAVE_LIBAVCODEC
|
||||||
if ( type == STREAM_MPEG )
|
if ( type == STREAM_MPEG )
|
||||||
|
|
|
@ -21,6 +21,9 @@
|
||||||
#include "zm_ffmpeg.h"
|
#include "zm_ffmpeg.h"
|
||||||
#include "zm_image.h"
|
#include "zm_image.h"
|
||||||
#include "zm_rgb.h"
|
#include "zm_rgb.h"
|
||||||
|
extern "C" {
|
||||||
|
#include "libavutil/pixdesc.h"
|
||||||
|
}
|
||||||
|
|
||||||
#if HAVE_LIBAVCODEC || HAVE_LIBAVUTIL || HAVE_LIBSWSCALE
|
#if HAVE_LIBAVCODEC || HAVE_LIBAVUTIL || HAVE_LIBSWSCALE
|
||||||
|
|
||||||
|
@ -70,14 +73,14 @@ static bool bInit = false;
|
||||||
void FFMPEGInit() {
|
void FFMPEGInit() {
|
||||||
|
|
||||||
if ( !bInit ) {
|
if ( !bInit ) {
|
||||||
if ( logDebugging() )
|
if ( logDebugging() && config.log_ffmpeg ) {
|
||||||
av_log_set_level( AV_LOG_DEBUG );
|
av_log_set_level( AV_LOG_DEBUG );
|
||||||
else
|
av_log_set_callback(log_libav_callback);
|
||||||
|
Info("Enabling ffmpeg logs, as LOG_DEBUG+LOG_FFMPEG are enabled in options");
|
||||||
|
} else {
|
||||||
|
Info("Not enabling ffmpeg logs, as LOG_FFMPEG and/or LOG_DEBUG is disabled in options, or this monitor not part of your debug targets");
|
||||||
av_log_set_level( AV_LOG_QUIET );
|
av_log_set_level( AV_LOG_QUIET );
|
||||||
if ( config.log_ffmpeg )
|
}
|
||||||
av_log_set_callback(log_libav_callback);
|
|
||||||
else
|
|
||||||
Info("Not enabling ffmpeg logs, as LOG_FFMPEG is disabled in options");
|
|
||||||
#if LIBAVFORMAT_VERSION_CHECK(58, 9, 0, 64, 0)
|
#if LIBAVFORMAT_VERSION_CHECK(58, 9, 0, 64, 0)
|
||||||
#else
|
#else
|
||||||
av_register_all();
|
av_register_all();
|
||||||
|
@ -284,26 +287,6 @@ static void zm_log_fps(double d, const char *postfix) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void zm_dump_frame(const AVFrame *frame,const char *text) {
|
|
||||||
Debug(1, "%s: format %d %s sample_rate %" PRIu32 " nb_samples %d channels %d"
|
|
||||||
" duration %" PRId64
|
|
||||||
" layout %d pts %" PRId64,
|
|
||||||
text,
|
|
||||||
frame->format,
|
|
||||||
av_get_sample_fmt_name((AVSampleFormat)frame->format),
|
|
||||||
frame->sample_rate,
|
|
||||||
frame->nb_samples,
|
|
||||||
#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100)
|
|
||||||
frame->channels,
|
|
||||||
frame->pkt_duration,
|
|
||||||
#else
|
|
||||||
0, 0,
|
|
||||||
#endif
|
|
||||||
frame->channel_layout,
|
|
||||||
frame->pts
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
||||||
void zm_dump_codecpar ( const AVCodecParameters *par ) {
|
void zm_dump_codecpar ( const AVCodecParameters *par ) {
|
||||||
Debug(1, "Dumping codecpar codec_type(%d) codec_id(%d) codec_tag(%d) width(%d) height(%d) bit_rate(%d) format(%d = %s)",
|
Debug(1, "Dumping codecpar codec_type(%d) codec_id(%d) codec_tag(%d) width(%d) height(%d) bit_rate(%d) format(%d = %s)",
|
||||||
|
@ -494,37 +477,20 @@ bool is_audio_context( AVCodecContext *codec_context ) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int zm_receive_frame( AVCodecContext *context, AVFrame *frame, AVPacket &packet ) {
|
int zm_receive_frame(AVCodecContext *context, AVFrame *frame, AVPacket &packet) {
|
||||||
int ret;
|
int ret;
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
||||||
if ( (ret = avcodec_send_packet(context, &packet)) < 0 ) {
|
if ( (ret = avcodec_send_packet(context, &packet)) < 0 ) {
|
||||||
Error( "Unable to send packet %s, continuing",
|
Error( "Unable to send packet %s, continuing",
|
||||||
av_make_error_string(ret).c_str() );
|
av_make_error_string(ret).c_str() );
|
||||||
return 0;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if HAVE_AVUTIL_HWCONTEXT_H
|
if ( (ret = avcodec_receive_frame(context, frame)) < 0 ) {
|
||||||
if ( hwaccel ) {
|
Error("Unable to send packet %s, continuing",
|
||||||
if ( (ret = avcodec_receive_frame(context, hwFrame)) < 0 ) {
|
av_make_error_string(ret).c_str());
|
||||||
Error( "Unable to receive frame %d: %s, continuing", streams[packet.stream_index].frame_count,
|
return ret;
|
||||||
av_make_error_string(ret).c_str() );
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if ( (ret = av_hwframe_transfer_data(frame, hwFrame, 0)) < 0 ) {
|
|
||||||
Error( "Unable to transfer frame at frame %d: %s, continuing", streams[packet.stream_index].frame_count,
|
|
||||||
av_make_error_string(ret).c_str() );
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
#endif
|
|
||||||
if ( (ret = avcodec_receive_frame(context, frame)) < 0 ) {
|
|
||||||
Error( "Unable to send packet %s, continuing", av_make_error_string(ret).c_str() );
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#if HAVE_AVUTIL_HWCONTEXT_H
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
# else
|
# else
|
||||||
int frameComplete = 0;
|
int frameComplete = 0;
|
||||||
while ( !frameComplete ) {
|
while ( !frameComplete ) {
|
||||||
|
@ -535,12 +501,51 @@ int zm_receive_frame( AVCodecContext *context, AVFrame *frame, AVPacket &packet
|
||||||
}
|
}
|
||||||
if ( ret < 0 ) {
|
if ( ret < 0 ) {
|
||||||
Error("Unable to decode frame: %s", av_make_error_string(ret).c_str());
|
Error("Unable to decode frame: %s", av_make_error_string(ret).c_str());
|
||||||
return 0;
|
return ret;
|
||||||
}
|
}
|
||||||
} // end while !frameComplete
|
} // end while !frameComplete
|
||||||
#endif
|
#endif
|
||||||
|
return 0;
|
||||||
|
} // end int zm_receive_frame(AVCodecContext *context, AVFrame *frame, AVPacket &packet)
|
||||||
|
|
||||||
|
int zm_send_frame(AVCodecContext *ctx, AVFrame *frame, AVPacket &packet) {
|
||||||
|
int ret;
|
||||||
|
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
||||||
|
if ( (ret = avcodec_send_frame(ctx, frame)) < 0 ) {
|
||||||
|
Error("Could not send frame (error '%s')",
|
||||||
|
av_make_error_string(ret).c_str());
|
||||||
|
zm_av_packet_unref(&packet);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( (ret = avcodec_receive_packet(ctx, &packet)) < 0 ) {
|
||||||
|
if ( AVERROR(EAGAIN) == ret ) {
|
||||||
|
// The codec may need more samples than it has, perfectly valid
|
||||||
|
Debug(2, "Codec not ready to give us a packet");
|
||||||
|
} else {
|
||||||
|
Error("Could not recieve packet (error %d = '%s')", ret,
|
||||||
|
av_make_error_string(ret).c_str());
|
||||||
|
}
|
||||||
|
zm_av_packet_unref(&packet);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
int data_present;
|
||||||
|
if ( (ret = avcodec_encode_audio2(
|
||||||
|
ctx, &packet, frame, &data_present)) < 0 ) {
|
||||||
|
Error("Could not encode frame (error '%s')",
|
||||||
|
av_make_error_string(ret).c_str());
|
||||||
|
zm_av_packet_unref(&packet);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if ( !data_present ) {
|
||||||
|
Debug(2, "Not ready to out a frame yet.");
|
||||||
|
zm_av_packet_unref(&packet);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return 1;
|
return 1;
|
||||||
} // end int zm_receive_frame( AVCodecContext *context, AVFrame *frame, AVPacket &packet )
|
} // wend zm_send_frame
|
||||||
|
|
||||||
void dumpPacket(AVStream *stream, AVPacket *pkt, const char *text) {
|
void dumpPacket(AVStream *stream, AVPacket *pkt, const char *text) {
|
||||||
char b[10240];
|
char b[10240];
|
||||||
|
|
|
@ -299,7 +299,45 @@ void zm_dump_codec(const AVCodecContext *codec);
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
||||||
void zm_dump_codecpar(const AVCodecParameters *par);
|
void zm_dump_codecpar(const AVCodecParameters *par);
|
||||||
#endif
|
#endif
|
||||||
void zm_dump_frame(const AVFrame *frame, const char *text="Frame");
|
#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100)
|
||||||
|
#define zm_dump_frame(frame, text) Debug(1, "%s: format %d %s sample_rate %" PRIu32 " nb_samples %d channels %d" \
|
||||||
|
" duration %" PRId64 \
|
||||||
|
" layout %d pts %" PRId64,\
|
||||||
|
text, \
|
||||||
|
frame->format, \
|
||||||
|
av_get_sample_fmt_name((AVSampleFormat)frame->format), \
|
||||||
|
frame->sample_rate, \
|
||||||
|
frame->nb_samples, \
|
||||||
|
frame->channels, \
|
||||||
|
frame->pkt_duration, \
|
||||||
|
frame->channel_layout, \
|
||||||
|
frame->pts \
|
||||||
|
);
|
||||||
|
#else
|
||||||
|
#define zm_dump_frame(frame, text) Debug(1, "%s: format %d %s sample_rate %" PRIu32 " nb_samples %d channels %d" \
|
||||||
|
" duration %" PRId64 \
|
||||||
|
" layout %d pts %" PRId64, \
|
||||||
|
text, \
|
||||||
|
frame->format, \
|
||||||
|
av_get_sample_fmt_name((AVSampleFormat)frame->format), \
|
||||||
|
frame->sample_rate, \
|
||||||
|
frame->nb_samples, \
|
||||||
|
0, 0, \
|
||||||
|
frame->channel_layout, \
|
||||||
|
frame->pts \
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define zm_dump_video_frame(frame,text) Debug(1, "%s: format %d %s %dx%d linesize:%dx%d pts: %" PRId64, \
|
||||||
|
text, \
|
||||||
|
frame->format, \
|
||||||
|
av_get_pix_fmt_name((AVPixelFormat)frame->format), \
|
||||||
|
frame->width, \
|
||||||
|
frame->height, \
|
||||||
|
frame->linesize[0], frame->linesize[1], \
|
||||||
|
frame->pts \
|
||||||
|
);
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100)
|
#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100)
|
||||||
#define zm_av_packet_unref(packet) av_packet_unref(packet)
|
#define zm_av_packet_unref(packet) av_packet_unref(packet)
|
||||||
|
@ -337,7 +375,9 @@ bool is_audio_stream(AVStream *);
|
||||||
bool is_video_context(AVCodec *);
|
bool is_video_context(AVCodec *);
|
||||||
bool is_audio_context(AVCodec *);
|
bool is_audio_context(AVCodec *);
|
||||||
|
|
||||||
int zm_receive_frame( AVCodecContext *context, AVFrame *frame, AVPacket &packet );
|
int zm_receive_frame(AVCodecContext *context, AVFrame *frame, AVPacket &packet);
|
||||||
|
int zm_send_frame(AVCodecContext *context, AVFrame *frame, AVPacket &packet);
|
||||||
|
|
||||||
void dumpPacket(AVStream *, AVPacket *,const char *text="");
|
void dumpPacket(AVStream *, AVPacket *,const char *text="");
|
||||||
void dumpPacket(AVPacket *,const char *text="");
|
void dumpPacket(AVPacket *,const char *text="");
|
||||||
#ifndef HAVE_LIBSWRESAMPLE
|
#ifndef HAVE_LIBSWRESAMPLE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// ZoneMinder Ffmpeg Camera Class Implementation, $Date: 2009-01-16 12:18:50 +0000 (Fri, 16 Jan 2009) $, $Revision: 2713 $
|
// ZoneMinder Ffmpeg Camera Class Implementation
|
||||||
// Copyright (C) 2001-2008 Philip Coombes
|
// Copyright (C) 2001-2008 Philip Coombes
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or
|
// This program is free software; you can redistribute it and/or
|
||||||
|
@ -26,61 +26,70 @@
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "libavutil/time.h"
|
#include "libavutil/time.h"
|
||||||
#if HAVE_AVUTIL_HWCONTEXT_H
|
#if HAVE_LIBAVUTIL_HWCONTEXT_H
|
||||||
#include "libavutil/hwcontext.h"
|
#include "libavutil/hwcontext.h"
|
||||||
#include "libavutil/hwcontext_qsv.h"
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "libavutil/pixdesc.h"
|
||||||
}
|
}
|
||||||
#ifndef AV_ERROR_MAX_STRING_SIZE
|
|
||||||
#define AV_ERROR_MAX_STRING_SIZE 64
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef SOLARIS
|
#include <string>
|
||||||
#include <sys/errno.h> // for ESRCH
|
#include <locale>
|
||||||
#include <signal.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
#if HAVE_LIBAVUTIL_HWCONTEXT_H
|
||||||
|
#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0)
|
||||||
|
static enum AVPixelFormat hw_pix_fmt;
|
||||||
|
static enum AVPixelFormat get_hw_format(
|
||||||
|
AVCodecContext *ctx,
|
||||||
|
const enum AVPixelFormat *pix_fmts
|
||||||
|
) {
|
||||||
|
const enum AVPixelFormat *p;
|
||||||
|
|
||||||
#if HAVE_AVUTIL_HWCONTEXT_H
|
for ( p = pix_fmts; *p != -1; p++ ) {
|
||||||
static AVPixelFormat get_format(AVCodecContext *avctx, const enum AVPixelFormat *pix_fmts) {
|
if ( *p == hw_pix_fmt )
|
||||||
while (*pix_fmts != AV_PIX_FMT_NONE) {
|
return *p;
|
||||||
if (*pix_fmts == AV_PIX_FMT_QSV) {
|
|
||||||
DecodeContext *decode = (DecodeContext *)avctx->opaque;
|
|
||||||
AVHWFramesContext *frames_ctx;
|
|
||||||
AVQSVFramesContext *frames_hwctx;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
/* create a pool of surfaces to be used by the decoder */
|
|
||||||
avctx->hw_frames_ctx = av_hwframe_ctx_alloc(decode->hw_device_ref);
|
|
||||||
if (!avctx->hw_frames_ctx)
|
|
||||||
return AV_PIX_FMT_NONE;
|
|
||||||
frames_ctx = (AVHWFramesContext*)avctx->hw_frames_ctx->data;
|
|
||||||
frames_hwctx = (AVQSVFramesContext*)frames_ctx->hwctx;
|
|
||||||
|
|
||||||
frames_ctx->format = AV_PIX_FMT_QSV;
|
|
||||||
frames_ctx->sw_format = avctx->sw_pix_fmt;
|
|
||||||
frames_ctx->width = FFALIGN(avctx->coded_width, 32);
|
|
||||||
frames_ctx->height = FFALIGN(avctx->coded_height, 32);
|
|
||||||
frames_ctx->initial_pool_size = 32;
|
|
||||||
|
|
||||||
frames_hwctx->frame_type = MFX_MEMTYPE_VIDEO_MEMORY_DECODER_TARGET;
|
|
||||||
|
|
||||||
ret = av_hwframe_ctx_init(avctx->hw_frames_ctx);
|
|
||||||
if (ret < 0)
|
|
||||||
return AV_PIX_FMT_NONE;
|
|
||||||
|
|
||||||
return AV_PIX_FMT_QSV;
|
|
||||||
}
|
|
||||||
|
|
||||||
pix_fmts++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Error( "The QSV pixel format not offered in get_format()");
|
Error("Failed to get HW surface format for %s.",
|
||||||
|
av_get_pix_fmt_name(hw_pix_fmt));
|
||||||
|
for ( p = pix_fmts; *p != -1; p++ )
|
||||||
|
Error("Available HW surface format was %s.",
|
||||||
|
av_get_pix_fmt_name(*p));
|
||||||
|
|
||||||
return AV_PIX_FMT_NONE;
|
return AV_PIX_FMT_NONE;
|
||||||
}
|
}
|
||||||
|
#if !LIBAVUTIL_VERSION_CHECK(56, 22, 0, 14, 0)
|
||||||
|
static enum AVPixelFormat find_fmt_by_hw_type(const enum AVHWDeviceType type) {
|
||||||
|
enum AVPixelFormat fmt;
|
||||||
|
switch (type) {
|
||||||
|
case AV_HWDEVICE_TYPE_VAAPI:
|
||||||
|
fmt = AV_PIX_FMT_VAAPI;
|
||||||
|
break;
|
||||||
|
case AV_HWDEVICE_TYPE_DXVA2:
|
||||||
|
fmt = AV_PIX_FMT_DXVA2_VLD;
|
||||||
|
break;
|
||||||
|
case AV_HWDEVICE_TYPE_D3D11VA:
|
||||||
|
fmt = AV_PIX_FMT_D3D11;
|
||||||
|
break;
|
||||||
|
case AV_HWDEVICE_TYPE_VDPAU:
|
||||||
|
fmt = AV_PIX_FMT_VDPAU;
|
||||||
|
break;
|
||||||
|
case AV_HWDEVICE_TYPE_CUDA:
|
||||||
|
fmt = AV_PIX_FMT_CUDA;
|
||||||
|
break;
|
||||||
|
case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
|
||||||
|
fmt = AV_PIX_FMT_VIDEOTOOLBOX;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fmt = AV_PIX_FMT_NONE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return fmt;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
FfmpegCamera::FfmpegCamera(
|
FfmpegCamera::FfmpegCamera(
|
||||||
int p_id,
|
int p_id,
|
||||||
|
@ -95,8 +104,9 @@ FfmpegCamera::FfmpegCamera(
|
||||||
int p_hue,
|
int p_hue,
|
||||||
int p_colour,
|
int p_colour,
|
||||||
bool p_capture,
|
bool p_capture,
|
||||||
bool p_record_audio
|
bool p_record_audio,
|
||||||
) :
|
const std::string &p_hwaccel_name,
|
||||||
|
const std::string &p_hwaccel_device) :
|
||||||
Camera(
|
Camera(
|
||||||
p_id,
|
p_id,
|
||||||
FFMPEG_SRC,
|
FFMPEG_SRC,
|
||||||
|
@ -111,20 +121,16 @@ FfmpegCamera::FfmpegCamera(
|
||||||
p_capture,
|
p_capture,
|
||||||
p_record_audio
|
p_record_audio
|
||||||
),
|
),
|
||||||
mPath( p_path ),
|
mPath(p_path),
|
||||||
mMethod( p_method ),
|
mMethod(p_method),
|
||||||
mOptions( p_options )
|
mOptions(p_options),
|
||||||
|
hwaccel_name(p_hwaccel_name),
|
||||||
|
hwaccel_device(p_hwaccel_device)
|
||||||
{
|
{
|
||||||
if ( capture ) {
|
if ( capture ) {
|
||||||
FFMPEGInit();
|
FFMPEGInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
hwaccel = false;
|
|
||||||
#if HAVE_AVUTIL_HWCONTEXT_H
|
|
||||||
decode = { NULL };
|
|
||||||
hwFrame = NULL;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
mFormatContext = NULL;
|
mFormatContext = NULL;
|
||||||
mVideoStreamId = -1;
|
mVideoStreamId = -1;
|
||||||
mAudioStreamId = -1;
|
mAudioStreamId = -1;
|
||||||
|
@ -137,11 +143,17 @@ FfmpegCamera::FfmpegCamera(
|
||||||
frameCount = 0;
|
frameCount = 0;
|
||||||
mCanCapture = false;
|
mCanCapture = false;
|
||||||
error_count = 0;
|
error_count = 0;
|
||||||
|
#if HAVE_LIBAVUTIL_HWCONTEXT_H
|
||||||
|
hwFrame = NULL;
|
||||||
|
hw_device_ctx = NULL;
|
||||||
|
#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0)
|
||||||
|
hw_pix_fmt = AV_PIX_FMT_NONE;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
} // end FFmpegCamera::FFmpegCamera
|
} // end FFmpegCamera::FFmpegCamera
|
||||||
|
|
||||||
FfmpegCamera::~FfmpegCamera() {
|
FfmpegCamera::~FfmpegCamera() {
|
||||||
|
|
||||||
Close();
|
Close();
|
||||||
|
|
||||||
FFMPEGDeInit();
|
FFMPEGDeInit();
|
||||||
|
@ -164,14 +176,11 @@ int FfmpegCamera::PreCapture() {
|
||||||
}
|
}
|
||||||
|
|
||||||
int FfmpegCamera::Capture(ZMPacket &zm_packet) {
|
int FfmpegCamera::Capture(ZMPacket &zm_packet) {
|
||||||
if ( ! mCanCapture ) {
|
if ( !mCanCapture )
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
// If the reopen thread has a value, but mCanCapture != 0, then we have just reopened the connection to the ffmpeg device, and we can clean up the thread.
|
|
||||||
|
|
||||||
if ( (ret = av_read_frame(mFormatContext, &packet)) < 0 ) {
|
if ( (ret = av_read_frame(mFormatContext, &packet)) < 0 ) {
|
||||||
if (
|
if (
|
||||||
// Check if EOF.
|
// Check if EOF.
|
||||||
|
@ -179,21 +188,15 @@ int FfmpegCamera::Capture(ZMPacket &zm_packet) {
|
||||||
// Check for Connection failure.
|
// Check for Connection failure.
|
||||||
(ret == -110)
|
(ret == -110)
|
||||||
) {
|
) {
|
||||||
Info("Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, ret,
|
Info("Unable to read packet from stream %d: error %d \"%s\".",
|
||||||
av_make_error_string(ret).c_str()
|
packet.stream_index, ret, av_make_error_string(ret).c_str());
|
||||||
);
|
} else {
|
||||||
} else {
|
Error("Unable to read packet from stream %d: error %d \"%s\".",
|
||||||
Error("Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, ret,
|
packet.stream_index, ret, av_make_error_string(ret).c_str());
|
||||||
av_make_error_string(ret).c_str()
|
}
|
||||||
);
|
return -1;
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
dumpPacket(mFormatContext->streams[packet.stream_index], &packet, "ffmpeg_camera in");
|
dumpPacket(mFormatContext->streams[packet.stream_index], &packet, "ffmpeg_camera in");
|
||||||
if ( 0 && ( packet.dts < 0 ) ) {
|
|
||||||
zm_av_packet_unref(&packet);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes += packet.size;
|
bytes += packet.size;
|
||||||
zm_packet.set_packet(&packet);
|
zm_packet.set_packet(&packet);
|
||||||
|
@ -207,58 +210,59 @@ int FfmpegCamera::PostCapture() {
|
||||||
}
|
}
|
||||||
|
|
||||||
int FfmpegCamera::OpenFfmpeg() {
|
int FfmpegCamera::OpenFfmpeg() {
|
||||||
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
error_count = 0;
|
error_count = 0;
|
||||||
|
|
||||||
// Open the input, not necessarily a file
|
// Open the input, not necessarily a file
|
||||||
#if !LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 4, 0)
|
#if !LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 4, 0)
|
||||||
Debug(1, "Calling av_open_input_file");
|
|
||||||
if ( av_open_input_file(&mFormatContext, mPath.c_str(), NULL, 0, NULL) != 0 )
|
if ( av_open_input_file(&mFormatContext, mPath.c_str(), NULL, 0, NULL) != 0 )
|
||||||
#else
|
#else
|
||||||
// Handle options
|
// Handle options
|
||||||
AVDictionary *opts = NULL;
|
AVDictionary *opts = NULL;
|
||||||
ret = av_dict_parse_string(&opts, Options().c_str(), "=", ",", 0);
|
ret = av_dict_parse_string(&opts, Options().c_str(), "=", ",", 0);
|
||||||
if ( ret < 0 ) {
|
if ( ret < 0 ) {
|
||||||
Warning("Could not parse ffmpeg input options list '%s'", Options().c_str());
|
Warning("Could not parse ffmpeg input options '%s'", Options().c_str());
|
||||||
} else {
|
|
||||||
Debug(2,"Could not parse ffmpeg input options list '%s'", Options().c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set transport method as specified by method field, rtpUni is default
|
std::locale locale;
|
||||||
const std::string method = Method();
|
if ( std::toupper(mPath.substr(0, 3), locale) == "RTSP" ) {
|
||||||
if ( method == "rtpMulti" ) {
|
// Set transport method as specified by method field, rtpUni is default
|
||||||
ret = av_dict_set(&opts, "rtsp_transport", "udp_multicast", 0);
|
const std::string method = Method();
|
||||||
} else if ( method == "rtpRtsp" ) {
|
if ( method == "rtpMulti" ) {
|
||||||
ret = av_dict_set(&opts, "rtsp_transport", "tcp", 0);
|
ret = av_dict_set(&opts, "rtsp_transport", "udp_multicast", 0);
|
||||||
} else if ( method == "rtpRtspHttp" ) {
|
} else if ( method == "rtpRtsp" ) {
|
||||||
ret = av_dict_set(&opts, "rtsp_transport", "http", 0);
|
ret = av_dict_set(&opts, "rtsp_transport", "tcp", 0);
|
||||||
} else if ( method == "rtpUni" ) {
|
} else if ( method == "rtpRtspHttp" ) {
|
||||||
ret = av_dict_set(&opts, "rtsp_transport", "udp", 0);
|
ret = av_dict_set(&opts, "rtsp_transport", "http", 0);
|
||||||
} else {
|
} else if ( method == "rtpUni" ) {
|
||||||
Warning("Unknown method (%s)", method.c_str());
|
ret = av_dict_set(&opts, "rtsp_transport", "udp", 0);
|
||||||
}
|
} else {
|
||||||
//#av_dict_set(&opts, "timeout", "10000000", 0); // in microseconds.
|
Warning("Unknown method (%s)", method.c_str());
|
||||||
|
}
|
||||||
|
// #av_dict_set(&opts, "timeout", "10000000", 0); // in microseconds.
|
||||||
|
|
||||||
if ( ret < 0 ) {
|
if ( ret < 0 ) {
|
||||||
Warning("Could not set rtsp_transport method '%s'", method.c_str());
|
Warning("Could not set rtsp_transport method '%s'", method.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug(1, "Calling avformat_open_input for %s", mPath.c_str());
|
Debug(1, "Calling avformat_open_input for %s", mPath.c_str());
|
||||||
|
|
||||||
mFormatContext = avformat_alloc_context( );
|
mFormatContext = avformat_alloc_context();
|
||||||
// Speed up find_stream_info
|
// Speed up find_stream_info
|
||||||
//FIXME can speed up initial analysis but need sensible parameters...
|
// FIXME can speed up initial analysis but need sensible parameters...
|
||||||
//mFormatContext->probesize = 32;
|
// mFormatContext->probesize = 32;
|
||||||
//mFormatContext->max_analyze_duration = 32;
|
// mFormatContext->max_analyze_duration = 32;
|
||||||
mFormatContext->interrupt_callback.callback = FfmpegInterruptCallback;
|
mFormatContext->interrupt_callback.callback = FfmpegInterruptCallback;
|
||||||
mFormatContext->interrupt_callback.opaque = this;
|
mFormatContext->interrupt_callback.opaque = this;
|
||||||
|
|
||||||
if ( avformat_open_input(&mFormatContext, mPath.c_str(), NULL, &opts) != 0 )
|
ret = avformat_open_input(&mFormatContext, mPath.c_str(), NULL, &opts);
|
||||||
|
if ( ret != 0 )
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
Error("Unable to open input %s due to: %s", mPath.c_str(), strerror(errno));
|
Error("Unable to open input %s due to: %s", mPath.c_str(),
|
||||||
|
av_make_error_string(ret).c_str());
|
||||||
#if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0)
|
#if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0)
|
||||||
av_close_input_file(mFormatContext);
|
av_close_input_file(mFormatContext);
|
||||||
#else
|
#else
|
||||||
|
@ -271,45 +275,32 @@ int FfmpegCamera::OpenFfmpeg() {
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
AVDictionaryEntry *e = NULL;
|
AVDictionaryEntry *e = NULL;
|
||||||
while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL ) {
|
while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL ) {
|
||||||
Warning("Option %s not recognized by ffmpeg", e->key);
|
Warning("Option %s not recognized by ffmpeg", e->key);
|
||||||
}
|
}
|
||||||
av_dict_free(&opts);
|
av_dict_free(&opts);
|
||||||
|
|
||||||
monitor->GetLastEventId() ;
|
|
||||||
|
|
||||||
Debug(1, "Opened input");
|
|
||||||
|
|
||||||
Info("Stream open %s, parsing streams...", mPath.c_str());
|
Info("Stream open %s, parsing streams...", mPath.c_str());
|
||||||
|
|
||||||
#if !LIBAVFORMAT_VERSION_CHECK(53, 6, 0, 6, 0)
|
#if !LIBAVFORMAT_VERSION_CHECK(53, 6, 0, 6, 0)
|
||||||
if ( av_find_stream_info(mFormatContext) < 0 )
|
ret = av_find_stream_info(mFormatContext);
|
||||||
#else
|
#else
|
||||||
if ( avformat_find_stream_info(mFormatContext, 0) < 0 )
|
ret = avformat_find_stream_info(mFormatContext, 0);
|
||||||
#endif
|
#endif
|
||||||
{
|
if ( ret < 0 ) {
|
||||||
Error("Unable to find stream info from %s due to: %s", mPath.c_str(), strerror(errno));
|
Error("Unable to find stream info from %s due to: %s",
|
||||||
|
mPath.c_str(), av_make_error_string(ret).c_str());
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug(4, "Got stream info");
|
|
||||||
|
|
||||||
// Find first video stream present
|
// Find first video stream present
|
||||||
// The one we want Might not be the first
|
// The one we want Might not be the first
|
||||||
mVideoStreamId = -1;
|
mVideoStreamId = -1;
|
||||||
mAudioStreamId = -1;
|
mAudioStreamId = -1;
|
||||||
for ( unsigned int i=0; i < mFormatContext->nb_streams; i++ ) {
|
for ( unsigned int i=0; i < mFormatContext->nb_streams; i++ ) {
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
AVStream *stream = mFormatContext->streams[i];
|
||||||
if ( mFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ) {
|
if ( is_video_stream(stream) ) {
|
||||||
#else
|
|
||||||
#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
|
|
||||||
if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO ) {
|
|
||||||
#else
|
|
||||||
if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) {
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
if ( mVideoStreamId == -1 ) {
|
if ( mVideoStreamId == -1 ) {
|
||||||
mVideoStreamId = i;
|
mVideoStreamId = i;
|
||||||
// if we break, then we won't find the audio stream
|
// if we break, then we won't find the audio stream
|
||||||
|
@ -317,122 +308,148 @@ int FfmpegCamera::OpenFfmpeg() {
|
||||||
} else {
|
} else {
|
||||||
Debug(2, "Have another video stream.");
|
Debug(2, "Have another video stream.");
|
||||||
}
|
}
|
||||||
}
|
} else if ( is_audio_stream(stream) ) {
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
|
||||||
if ( mFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO ) {
|
|
||||||
#else
|
|
||||||
#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
|
|
||||||
if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO ) {
|
|
||||||
#else
|
|
||||||
if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO ) {
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
if ( mAudioStreamId == -1 ) {
|
if ( mAudioStreamId == -1 ) {
|
||||||
mAudioStreamId = i;
|
mAudioStreamId = i;
|
||||||
} else {
|
} else {
|
||||||
Debug(2, "Have another audio stream.");
|
Debug(2, "Have another audio stream.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // end foreach stream
|
} // end foreach stream
|
||||||
if ( mVideoStreamId == -1 )
|
if ( mVideoStreamId == -1 )
|
||||||
Fatal("Unable to locate video stream in %s", mPath.c_str());
|
Fatal("Unable to locate video stream in %s", mPath.c_str());
|
||||||
if ( mAudioStreamId == -1 )
|
|
||||||
Debug(3, "Unable to locate audio stream in %s", mPath.c_str());
|
|
||||||
|
|
||||||
Debug(3, "Found video stream at index %d", mVideoStreamId);
|
Debug(3, "Found video stream at index %d, audio stream at index %d",
|
||||||
Debug(3, "Found audio stream at index %d", mAudioStreamId);
|
mVideoStreamId, mAudioStreamId);
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
||||||
//mVideoCodecContext = avcodec_alloc_context3(NULL);
|
// mVideoCodecContext = avcodec_alloc_context3(NULL);
|
||||||
//avcodec_parameters_to_context( mVideoCodecContext, mFormatContext->streams[mVideoStreamId]->codecpar );
|
// avcodec_parameters_to_context(mVideoCodecContext,
|
||||||
|
// mFormatContext->streams[mVideoStreamId]->codecpar);
|
||||||
// this isn't copied.
|
// this isn't copied.
|
||||||
//mVideoCodecContext->time_base = mFormatContext->streams[mVideoStreamId]->codec->time_base;
|
// mVideoCodecContext->time_base =
|
||||||
|
// mFormatContext->streams[mVideoStreamId]->codec->time_base;
|
||||||
#else
|
#else
|
||||||
#endif
|
#endif
|
||||||
mVideoCodecContext = mFormatContext->streams[mVideoStreamId]->codec;
|
mVideoCodecContext = mFormatContext->streams[mVideoStreamId]->codec;
|
||||||
// STolen from ispy
|
|
||||||
//this fixes issues with rtsp streams!! woot.
|
|
||||||
//mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG2_CHUNKS | CODEC_FLAG_LOW_DELAY; // Enable faster H264 decode.
|
|
||||||
#ifdef CODEC_FLAG2_FAST
|
#ifdef CODEC_FLAG2_FAST
|
||||||
mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG_LOW_DELAY;
|
mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG_LOW_DELAY;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if HAVE_AVUTIL_HWCONTEXT_H
|
|
||||||
if ( mVideoCodecContext->codec_id == AV_CODEC_ID_H264 ) {
|
if ( mVideoCodecContext->codec_id == AV_CODEC_ID_H264 ) {
|
||||||
|
if ( (mVideoCodec = avcodec_find_decoder_by_name("h264_mmal")) == NULL ) {
|
||||||
//vaapi_decoder = new VAAPIDecoder();
|
|
||||||
//mVideoCodecContext->opaque = vaapi_decoder;
|
|
||||||
//mVideoCodec = vaapi_decoder->openCodec( mVideoCodecContext );
|
|
||||||
|
|
||||||
if ( ! mVideoCodec ) {
|
|
||||||
// Try to open an hwaccel codec.
|
|
||||||
if ( (mVideoCodec = avcodec_find_decoder_by_name("h264_vaapi")) == NULL ) {
|
|
||||||
Debug(1, "Failed to find decoder (h264_vaapi)" );
|
|
||||||
} else {
|
|
||||||
Debug(1, "Success finding decoder (h264_vaapi)" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( ! mVideoCodec ) {
|
|
||||||
// Try to open an hwaccel codec.
|
|
||||||
if ( (mVideoCodec = avcodec_find_decoder_by_name("h264_qsv")) == NULL ) {
|
|
||||||
Debug(1, "Failed to find decoder (h264_qsv)" );
|
|
||||||
} else {
|
|
||||||
Debug(1, "Success finding decoder (h264_qsv)" );
|
|
||||||
/* open the hardware device */
|
|
||||||
ret = av_hwdevice_ctx_create(&decode.hw_device_ref, AV_HWDEVICE_TYPE_QSV,
|
|
||||||
"auto", NULL, 0);
|
|
||||||
if (ret < 0) {
|
|
||||||
Error("Failed to open the hardware device");
|
|
||||||
mVideoCodec = NULL;
|
|
||||||
} else {
|
|
||||||
mVideoCodecContext->opaque = &decode;
|
|
||||||
mVideoCodecContext->get_format = get_format;
|
|
||||||
hwaccel = true;
|
|
||||||
hwFrame = zm_av_frame_alloc();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // end if h264
|
|
||||||
#endif
|
|
||||||
if ( mVideoCodecContext->codec_id == AV_CODEC_ID_H264 ) {
|
|
||||||
if ( (mVideoCodec = avcodec_find_decoder_by_name("h264_mmal")) == NULL ) {
|
|
||||||
Debug(1, "Failed to find decoder (h264_mmal)");
|
Debug(1, "Failed to find decoder (h264_mmal)");
|
||||||
} else {
|
} else {
|
||||||
Debug(1, "Success finding decoder (h264_mmal)");
|
Debug(1, "Success finding decoder (h264_mmal)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( (!mVideoCodec) and ( (mVideoCodec = avcodec_find_decoder(mVideoCodecContext->codec_id)) == NULL ) ) {
|
if ( !mVideoCodec ) {
|
||||||
// Try and get the codec from the codec context
|
mVideoCodec = avcodec_find_decoder(mVideoCodecContext->codec_id);
|
||||||
Error("Can't find codec for video stream from %s", mPath.c_str());
|
if ( !mVideoCodec ) {
|
||||||
return -1;
|
// Try and get the codec from the codec context
|
||||||
} else {
|
Error("Can't find codec for video stream from %s", mPath.c_str());
|
||||||
Debug(1, "Video Found decoder %s", mVideoCodec->name);
|
|
||||||
zm_dump_stream_format(mFormatContext, mVideoStreamId, 0, 0);
|
|
||||||
|
|
||||||
// Open the codec
|
|
||||||
#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0)
|
|
||||||
ret = avcodec_open(mVideoCodecContext, mVideoCodec);
|
|
||||||
#else
|
|
||||||
ret = avcodec_open2(mVideoCodecContext, mVideoCodec, &opts);
|
|
||||||
#endif
|
|
||||||
AVDictionaryEntry *e = NULL;
|
|
||||||
while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL ) {
|
|
||||||
Warning( "Option %s not recognized by ffmpeg", e->key);
|
|
||||||
}
|
|
||||||
if ( ret < 0 ) {
|
|
||||||
Error("Unable to open codec for video stream from %s", mPath.c_str());
|
|
||||||
av_dict_free(&opts);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
zm_dump_codec(mVideoCodecContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Debug(1, "Video Found decoder %s", mVideoCodec->name);
|
||||||
|
zm_dump_stream_format(mFormatContext, mVideoStreamId, 0, 0);
|
||||||
|
|
||||||
|
if ( hwaccel_name != "" ) {
|
||||||
|
#if HAVE_LIBAVUTIL_HWCONTEXT_H
|
||||||
|
// 3.2 doesn't seem to have all the bits in place, so let's require 3.3 and up
|
||||||
|
#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0)
|
||||||
|
// Print out available types
|
||||||
|
enum AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;
|
||||||
|
while ( (type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE )
|
||||||
|
Debug(1, "%s", av_hwdevice_get_type_name(type));
|
||||||
|
|
||||||
|
const char *hw_name = hwaccel_name.c_str();
|
||||||
|
type = av_hwdevice_find_type_by_name(hw_name);
|
||||||
|
if ( type == AV_HWDEVICE_TYPE_NONE ) {
|
||||||
|
Debug(1, "Device type %s is not supported.", hw_name);
|
||||||
|
} else {
|
||||||
|
Debug(1, "Found hwdevice %s", av_hwdevice_get_type_name(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if LIBAVUTIL_VERSION_CHECK(56, 22, 0, 14, 0)
|
||||||
|
// Get h_pix_fmt
|
||||||
|
for ( int i = 0;; i++ ) {
|
||||||
|
const AVCodecHWConfig *config = avcodec_get_hw_config(mVideoCodec, i);
|
||||||
|
if ( !config ) {
|
||||||
|
Debug(1, "Decoder %s does not support device type %s.",
|
||||||
|
mVideoCodec->name, av_hwdevice_get_type_name(type));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ( (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)
|
||||||
|
&& (config->device_type == type)
|
||||||
|
) {
|
||||||
|
hw_pix_fmt = config->pix_fmt;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
Debug(1, "decoder %s hwConfig doesn't match our type: %s, pix_fmt %s.",
|
||||||
|
mVideoCodec->name,
|
||||||
|
av_hwdevice_get_type_name(config->device_type),
|
||||||
|
av_get_pix_fmt_name(config->pix_fmt)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} // end foreach hwconfig
|
||||||
|
#else
|
||||||
|
hw_pix_fmt = find_fmt_by_hw_type(type);
|
||||||
|
#endif
|
||||||
|
if ( hw_pix_fmt != AV_PIX_FMT_NONE ) {
|
||||||
|
Debug(1, "Selected gw_pix_fmt %d %s",
|
||||||
|
hw_pix_fmt,
|
||||||
|
av_get_pix_fmt_name(hw_pix_fmt));
|
||||||
|
|
||||||
|
mVideoCodecContext->get_format = get_hw_format;
|
||||||
|
|
||||||
|
Debug(1, "Creating hwdevice for %s",
|
||||||
|
(hwaccel_device != "" ? hwaccel_device.c_str() : ""));
|
||||||
|
ret = av_hwdevice_ctx_create(&hw_device_ctx, type,
|
||||||
|
(hwaccel_device != "" ? hwaccel_device.c_str(): NULL), NULL, 0);
|
||||||
|
if ( ret < 0 ) {
|
||||||
|
Error("Failed to create specified HW device.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
Debug(1, "Created hwdevice");
|
||||||
|
mVideoCodecContext->hw_device_ctx = av_buffer_ref(hw_device_ctx);
|
||||||
|
hwaccel = true;
|
||||||
|
hwFrame = zm_av_frame_alloc();
|
||||||
|
} else {
|
||||||
|
Debug(1, "Failed to setup hwaccel.");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
Debug(1, "AVCodec not new enough for hwaccel");
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
Warning("HWAccel support not compiled in.");
|
||||||
|
#endif
|
||||||
|
} // end if hwacel_name
|
||||||
|
|
||||||
|
#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0)
|
||||||
|
ret = avcodec_open(mVideoCodecContext, mVideoCodec);
|
||||||
|
#else
|
||||||
|
ret = avcodec_open2(mVideoCodecContext, mVideoCodec, &opts);
|
||||||
|
#endif
|
||||||
|
e = NULL;
|
||||||
|
while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL ) {
|
||||||
|
Warning("Option %s not recognized by ffmpeg", e->key);
|
||||||
|
}
|
||||||
|
if ( ret < 0 ) {
|
||||||
|
Error("Unable to open codec for video stream from %s", mPath.c_str());
|
||||||
|
av_dict_free(&opts);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
zm_dump_codec(mVideoCodecContext);
|
||||||
|
|
||||||
if ( mVideoCodecContext->hwaccel != NULL ) {
|
if ( mVideoCodecContext->hwaccel != NULL ) {
|
||||||
Debug(1, "HWACCEL in use");
|
Debug(1, "HWACCEL in use");
|
||||||
} else {
|
} else {
|
||||||
Debug(1, "HWACCEL not in use");
|
Debug(1, "HWACCEL not in use");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( mAudioStreamId >= 0 ) {
|
if ( mAudioStreamId >= 0 ) {
|
||||||
if ( (mAudioCodec = avcodec_find_decoder(
|
if ( (mAudioCodec = avcodec_find_decoder(
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
||||||
|
@ -445,29 +462,36 @@ int FfmpegCamera::OpenFfmpeg() {
|
||||||
} else {
|
} else {
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
||||||
mAudioCodecContext = avcodec_alloc_context3(mAudioCodec);
|
mAudioCodecContext = avcodec_alloc_context3(mAudioCodec);
|
||||||
avcodec_parameters_to_context( mAudioCodecContext, mFormatContext->streams[mAudioStreamId]->codecpar );
|
avcodec_parameters_to_context(
|
||||||
|
mAudioCodecContext,
|
||||||
|
mFormatContext->streams[mAudioStreamId]->codecpar
|
||||||
|
);
|
||||||
#else
|
#else
|
||||||
mAudioCodecContext = mFormatContext->streams[mAudioStreamId]->codec;
|
mAudioCodecContext = mFormatContext->streams[mAudioStreamId]->codec;
|
||||||
// = avcodec_alloc_context3(mAudioCodec);
|
// = avcodec_alloc_context3(mAudioCodec);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Debug(1, "Audio Found decoder");
|
|
||||||
zm_dump_stream_format(mFormatContext, mAudioStreamId, 0, 0);
|
zm_dump_stream_format(mFormatContext, mAudioStreamId, 0, 0);
|
||||||
// Open the codec
|
// Open the codec
|
||||||
#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0)
|
#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0)
|
||||||
Debug(1, "Calling avcodec_open");
|
|
||||||
if ( avcodec_open(mAudioCodecContext, mAudioCodec) < 0 )
|
if ( avcodec_open(mAudioCodecContext, mAudioCodec) < 0 )
|
||||||
#else
|
#else
|
||||||
Debug(1, "Calling avcodec_open2");
|
|
||||||
if ( avcodec_open2(mAudioCodecContext, mAudioCodec, 0) < 0 )
|
if ( avcodec_open2(mAudioCodecContext, mAudioCodec, 0) < 0 )
|
||||||
#endif
|
#endif
|
||||||
Fatal("Unable to open codec for video stream from %s", mPath.c_str());
|
{
|
||||||
}
|
Error("Unable to open codec for audio stream from %s", mPath.c_str());
|
||||||
Debug(1, "Opened audio codec");
|
return -1;
|
||||||
} // end if have audio stream
|
} // end if opened
|
||||||
|
} // end if found decoder
|
||||||
|
} // end if have audio stream
|
||||||
|
|
||||||
if ( (unsigned int)mVideoCodecContext->width != width || (unsigned int)mVideoCodecContext->height != height ) {
|
if (
|
||||||
Warning("Monitor dimensions are %dx%d but camera is sending %dx%d", width, height, mVideoCodecContext->width, mVideoCodecContext->height);
|
((unsigned int)mVideoCodecContext->width != width)
|
||||||
|
||
|
||||||
|
((unsigned int)mVideoCodecContext->height != height)
|
||||||
|
) {
|
||||||
|
Warning("Monitor dimensions are %dx%d but camera is sending %dx%d",
|
||||||
|
width, height, mVideoCodecContext->width, mVideoCodecContext->height);
|
||||||
}
|
}
|
||||||
|
|
||||||
mCanCapture = true;
|
mCanCapture = true;
|
||||||
|
@ -476,36 +500,44 @@ int FfmpegCamera::OpenFfmpeg() {
|
||||||
} // int FfmpegCamera::OpenFfmpeg()
|
} // int FfmpegCamera::OpenFfmpeg()
|
||||||
|
|
||||||
int FfmpegCamera::Close() {
|
int FfmpegCamera::Close() {
|
||||||
|
|
||||||
Debug(2, "CloseFfmpeg called.");
|
|
||||||
|
|
||||||
mCanCapture = false;
|
mCanCapture = false;
|
||||||
|
|
||||||
if ( mFrame ) {
|
if ( mFrame ) {
|
||||||
av_frame_free( &mFrame );
|
av_frame_free(&mFrame);
|
||||||
mFrame = NULL;
|
mFrame = NULL;
|
||||||
}
|
}
|
||||||
if ( mRawFrame ) {
|
if ( mRawFrame ) {
|
||||||
av_frame_free( &mRawFrame );
|
av_frame_free(&mRawFrame);
|
||||||
mRawFrame = NULL;
|
mRawFrame = NULL;
|
||||||
}
|
}
|
||||||
|
#if HAVE_LIBAVUTIL_HWCONTEXT_H
|
||||||
|
if ( hwFrame ) {
|
||||||
|
av_frame_free(&hwFrame);
|
||||||
|
hwFrame = NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if ( mVideoCodecContext ) {
|
if ( mVideoCodecContext ) {
|
||||||
avcodec_close(mVideoCodecContext);
|
avcodec_close(mVideoCodecContext);
|
||||||
Debug(1,"After codec close");
|
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
||||||
//avcodec_free_context(&mVideoCodecContext);
|
// avcodec_free_context(&mVideoCodecContext);
|
||||||
#endif
|
#endif
|
||||||
mVideoCodecContext = NULL; // Freed by av_close_input_file
|
mVideoCodecContext = NULL; // Freed by av_close_input_file
|
||||||
}
|
}
|
||||||
if ( mAudioCodecContext ) {
|
if ( mAudioCodecContext ) {
|
||||||
avcodec_close(mAudioCodecContext);
|
avcodec_close(mAudioCodecContext);
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
||||||
avcodec_free_context(&mAudioCodecContext);
|
avcodec_free_context(&mAudioCodecContext);
|
||||||
#endif
|
#endif
|
||||||
mAudioCodecContext = NULL; // Freed by av_close_input_file
|
mAudioCodecContext = NULL; // Freed by av_close_input_file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if HAVE_LIBAVUTIL_HWCONTEXT_H
|
||||||
|
if ( hw_device_ctx ) {
|
||||||
|
av_buffer_unref(&hw_device_ctx);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if ( mFormatContext ) {
|
if ( mFormatContext ) {
|
||||||
#if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0)
|
#if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0)
|
||||||
av_close_input_file(mFormatContext);
|
av_close_input_file(mFormatContext);
|
||||||
|
@ -516,12 +548,12 @@ int FfmpegCamera::Close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
} // end FfmpegCamera::Close
|
} // end FfmpegCamera::Close
|
||||||
|
|
||||||
int FfmpegCamera::FfmpegInterruptCallback(void *ctx) {
|
int FfmpegCamera::FfmpegInterruptCallback(void *ctx) {
|
||||||
//FfmpegCamera* camera = reinterpret_cast<FfmpegCamera*>(ctx);
|
// FfmpegCamera* camera = reinterpret_cast<FfmpegCamera*>(ctx);
|
||||||
//Debug(4, "FfmpegInterruptCallback");
|
// Debug(4, "FfmpegInterruptCallback");
|
||||||
return zm_terminate;
|
return zm_terminate;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // HAVE_LIBAVFORMAT
|
#endif // HAVE_LIBAVFORMAT
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
#include "zm_ffmpeg.h"
|
#include "zm_ffmpeg.h"
|
||||||
#include "zm_videostore.h"
|
#include "zm_videostore.h"
|
||||||
|
|
||||||
#if HAVE_AVUTIL_HWCONTEXT_H
|
#if HAVE_LIBAVUTIL_HWCONTEXT_H
|
||||||
typedef struct DecodeContext {
|
typedef struct DecodeContext {
|
||||||
AVBufferRef *hw_device_ref;
|
AVBufferRef *hw_device_ref;
|
||||||
} DecodeContext;
|
} DecodeContext;
|
||||||
|
@ -40,7 +40,10 @@ class FfmpegCamera : public Camera {
|
||||||
std::string mPath;
|
std::string mPath;
|
||||||
std::string mMethod;
|
std::string mMethod;
|
||||||
std::string mOptions;
|
std::string mOptions;
|
||||||
|
|
||||||
std::string encoder_options;
|
std::string encoder_options;
|
||||||
|
std::string hwaccel_name;
|
||||||
|
std::string hwaccel_device;
|
||||||
|
|
||||||
int frameCount;
|
int frameCount;
|
||||||
|
|
||||||
|
@ -51,17 +54,14 @@ class FfmpegCamera : public Camera {
|
||||||
AVFrame *mRawFrame;
|
AVFrame *mRawFrame;
|
||||||
AVFrame *mFrame;
|
AVFrame *mFrame;
|
||||||
|
|
||||||
bool hwaccel;
|
AVFrame *input_frame; // Use to point to mRawFrame or hwFrame;
|
||||||
#if HAVE_AVUTIL_HWCONTEXT_H
|
|
||||||
AVFrame *hwFrame;
|
|
||||||
DecodeContext decode;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Need to keep track of these because apparently the stream can start with values for pts/dts and then subsequent packets start at zero.
|
bool hwaccel;
|
||||||
int64_t audio_last_pts;
|
AVFrame *hwFrame;
|
||||||
int64_t audio_last_dts;
|
#if HAVE_LIBAVUTIL_HWCONTEXT_H
|
||||||
int64_t video_last_pts;
|
DecodeContext decode;
|
||||||
int64_t video_last_dts;
|
AVBufferRef *hw_device_ctx = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Used to store the incoming packet, it will get copied when queued.
|
// Used to store the incoming packet, it will get copied when queued.
|
||||||
// We only ever need one at a time, so instead of constantly allocating
|
// We only ever need one at a time, so instead of constantly allocating
|
||||||
|
@ -73,6 +73,10 @@ class FfmpegCamera : public Camera {
|
||||||
bool mCanCapture;
|
bool mCanCapture;
|
||||||
#endif // HAVE_LIBAVFORMAT
|
#endif // HAVE_LIBAVFORMAT
|
||||||
|
|
||||||
|
#if HAVE_LIBSWSCALE
|
||||||
|
struct SwsContext *mConvertContext;
|
||||||
|
#endif
|
||||||
|
|
||||||
int error_count;
|
int error_count;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -89,12 +93,15 @@ class FfmpegCamera : public Camera {
|
||||||
int p_hue,
|
int p_hue,
|
||||||
int p_colour,
|
int p_colour,
|
||||||
bool p_capture,
|
bool p_capture,
|
||||||
bool p_record_audio );
|
bool p_record_audio,
|
||||||
|
const std::string &p_hwaccel_name,
|
||||||
|
const std::string &p_hwaccel_device
|
||||||
|
);
|
||||||
~FfmpegCamera();
|
~FfmpegCamera();
|
||||||
|
|
||||||
const std::string &Path() const { return( mPath ); }
|
const std::string &Path() const { return mPath; }
|
||||||
const std::string &Options() const { return( mOptions ); }
|
const std::string &Options() const { return mOptions; }
|
||||||
const std::string &Method() const { return( mMethod ); }
|
const std::string &Method() const { return mMethod; }
|
||||||
|
|
||||||
int PrimeCapture();
|
int PrimeCapture();
|
||||||
int PreCapture();
|
int PreCapture();
|
||||||
|
@ -110,9 +117,10 @@ class FfmpegCamera : public Camera {
|
||||||
return mFormatContext->streams[mAudioStreamId];
|
return mFormatContext->streams[mAudioStreamId];
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
AVCodecContext *get_VideoCodecContext() { return mVideoCodecContext; };
|
AVCodecContext *get_VideoCodecContext() { return mVideoCodecContext; };
|
||||||
AVCodecContext *get_AudioCodecContext() { return mAudioCodecContext; };
|
AVCodecContext *get_AudioCodecContext() { return mAudioCodecContext; };
|
||||||
private:
|
private:
|
||||||
static int FfmpegInterruptCallback(void*ctx);
|
static int FfmpegInterruptCallback(void*ctx);
|
||||||
|
int transfer_to_image(Image &i, AVFrame *output_frame, AVFrame *input_frame);
|
||||||
};
|
};
|
||||||
#endif // ZM_FFMPEG_CAMERA_H
|
#endif // ZM_FFMPEG_CAMERA_H
|
||||||
|
|
|
@ -7,8 +7,7 @@ FFmpeg_Input::FFmpeg_Input() {
|
||||||
input_format_context = NULL;
|
input_format_context = NULL;
|
||||||
video_stream_id = -1;
|
video_stream_id = -1;
|
||||||
audio_stream_id = -1;
|
audio_stream_id = -1;
|
||||||
av_register_all();
|
FFMPEGInit();
|
||||||
avcodec_register_all();
|
|
||||||
streams = NULL;
|
streams = NULL;
|
||||||
frame = NULL;
|
frame = NULL;
|
||||||
}
|
}
|
||||||
|
@ -23,17 +22,18 @@ FFmpeg_Input::~FFmpeg_Input() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int FFmpeg_Input::Open( const char *filepath ) {
|
int FFmpeg_Input::Open(const char *filepath) {
|
||||||
|
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
/** Open the input file to read from it. */
|
/** Open the input file to read from it. */
|
||||||
if ( (error = avformat_open_input( &input_format_context, filepath, NULL, NULL)) < 0 ) {
|
error = avformat_open_input(&input_format_context, filepath, NULL, NULL);
|
||||||
|
if ( error < 0 ) {
|
||||||
Error("Could not open input file '%s' (error '%s')",
|
Error("Could not open input file '%s' (error '%s')",
|
||||||
filepath, av_make_error_string(error).c_str());
|
filepath, av_make_error_string(error).c_str());
|
||||||
input_format_context = NULL;
|
input_format_context = NULL;
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get information on the input file (number of streams etc.). */
|
/** Get information on the input file (number of streams etc.). */
|
||||||
if ( (error = avformat_find_stream_info(input_format_context, NULL)) < 0 ) {
|
if ( (error = avformat_find_stream_info(input_format_context, NULL)) < 0 ) {
|
||||||
|
@ -46,10 +46,10 @@ int FFmpeg_Input::Open( const char *filepath ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
streams = new stream[input_format_context->nb_streams];
|
streams = new stream[input_format_context->nb_streams];
|
||||||
Debug(2,"Have %d streams", input_format_context->nb_streams);
|
Debug(2, "Have %d streams", input_format_context->nb_streams);
|
||||||
|
|
||||||
for ( unsigned int i = 0; i < input_format_context->nb_streams; i += 1 ) {
|
for ( unsigned int i = 0; i < input_format_context->nb_streams; i += 1 ) {
|
||||||
if ( is_video_stream( input_format_context->streams[i] ) ) {
|
if ( is_video_stream(input_format_context->streams[i]) ) {
|
||||||
zm_dump_stream_format(input_format_context, i, 0, 0);
|
zm_dump_stream_format(input_format_context, i, 0, 0);
|
||||||
if ( video_stream_id == -1 ) {
|
if ( video_stream_id == -1 ) {
|
||||||
video_stream_id = i;
|
video_stream_id = i;
|
||||||
|
@ -59,7 +59,7 @@ int FFmpeg_Input::Open( const char *filepath ) {
|
||||||
}
|
}
|
||||||
} else if ( is_audio_stream(input_format_context->streams[i]) ) {
|
} else if ( is_audio_stream(input_format_context->streams[i]) ) {
|
||||||
if ( audio_stream_id == -1 ) {
|
if ( audio_stream_id == -1 ) {
|
||||||
Debug(2,"Audio stream is %d", i);
|
Debug(2, "Audio stream is %d", i);
|
||||||
audio_stream_id = i;
|
audio_stream_id = i;
|
||||||
} else {
|
} else {
|
||||||
Warning("Have another audio stream.");
|
Warning("Have another audio stream.");
|
||||||
|
@ -77,15 +77,16 @@ int FFmpeg_Input::Open( const char *filepath ) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if ( !(streams[i].codec = avcodec_find_decoder(streams[i].context->codec_id)) ) {
|
if ( !(streams[i].codec = avcodec_find_decoder(streams[i].context->codec_id)) ) {
|
||||||
Error("Could not find input codec\n");
|
Error("Could not find input codec");
|
||||||
avformat_close_input(&input_format_context);
|
avformat_close_input(&input_format_context);
|
||||||
return AVERROR_EXIT;
|
return AVERROR_EXIT;
|
||||||
} else {
|
} else {
|
||||||
Debug(1, "Using codec (%s) for stream %d", streams[i].codec->name, i);
|
Debug(1, "Using codec (%s) for stream %d", streams[i].codec->name, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((error = avcodec_open2( streams[i].context, streams[i].codec, NULL)) < 0) {
|
error = avcodec_open2(streams[i].context, streams[i].codec, NULL);
|
||||||
Error("Could not open input codec (error '%s')\n",
|
if ( error < 0 ) {
|
||||||
|
Error("Could not open input codec (error '%s')",
|
||||||
av_make_error_string(error).c_str());
|
av_make_error_string(error).c_str());
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
||||||
avcodec_free_context(&streams[i].context);
|
avcodec_free_context(&streams[i].context);
|
||||||
|
@ -125,13 +126,12 @@ int FFmpeg_Input::Close( ) {
|
||||||
return 1;
|
return 1;
|
||||||
} // end int FFmpeg_Input::Close()
|
} // end int FFmpeg_Input::Close()
|
||||||
|
|
||||||
AVFrame *FFmpeg_Input::get_frame( int stream_id ) {
|
AVFrame *FFmpeg_Input::get_frame(int stream_id) {
|
||||||
Debug(1, "Getting frame from stream %d", stream_id);
|
Debug(1, "Getting frame from stream %d", stream_id);
|
||||||
|
|
||||||
int frameComplete = false;
|
int frameComplete = false;
|
||||||
AVPacket packet;
|
AVPacket packet;
|
||||||
av_init_packet(&packet);
|
av_init_packet(&packet);
|
||||||
char errbuf[AV_ERROR_MAX_STRING_SIZE];
|
|
||||||
|
|
||||||
while ( !frameComplete ) {
|
while ( !frameComplete ) {
|
||||||
|
|
||||||
|
@ -144,10 +144,10 @@ AVFrame *FFmpeg_Input::get_frame( int stream_id ) {
|
||||||
(ret == -110)
|
(ret == -110)
|
||||||
) {
|
) {
|
||||||
Info("av_read_frame returned %s.", av_make_error_string(ret).c_str());
|
Info("av_read_frame returned %s.", av_make_error_string(ret).c_str());
|
||||||
} else {
|
return NULL;
|
||||||
Error("Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, ret,
|
|
||||||
av_make_error_string(ret).c_str());
|
|
||||||
}
|
}
|
||||||
|
Error("Unable to read packet from stream %d: error %d \"%s\".",
|
||||||
|
packet.stream_index, ret, av_make_error_string(ret).c_str());
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
dumpPacket(input_format_context->streams[packet.stream_index], &packet, "Received packet");
|
dumpPacket(input_format_context->streams[packet.stream_index], &packet, "Received packet");
|
||||||
|
@ -157,78 +157,24 @@ AVFrame *FFmpeg_Input::get_frame( int stream_id ) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug(3,"Packet is for our stream (%d)", packet.stream_index );
|
|
||||||
|
|
||||||
AVCodecContext *context = streams[packet.stream_index].context;
|
AVCodecContext *context = streams[packet.stream_index].context;
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
|
||||||
ret = avcodec_send_packet(context, &packet);
|
|
||||||
if ( ret < 0 ) {
|
|
||||||
av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
|
|
||||||
Error("Unable to send packet at frame %d: %s, continuing",
|
|
||||||
streams[packet.stream_index].frame_count, errbuf);
|
|
||||||
zm_av_packet_unref(&packet);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if HAVE_AVUTIL_HWCONTEXT_H
|
|
||||||
if ( hwaccel ) {
|
|
||||||
ret = avcodec_receive_frame( context, hwFrame );
|
|
||||||
if ( ret < 0 ) {
|
|
||||||
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
|
|
||||||
Error( "Unable to receive frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
|
|
||||||
zm_av_packet_unref( &packet );
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ret = av_hwframe_transfer_data(frame, hwFrame, 0);
|
|
||||||
if (ret < 0) {
|
|
||||||
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
|
|
||||||
Error( "Unable to transfer frame at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
|
|
||||||
zm_av_packet_unref(&packet);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
#endif
|
|
||||||
if ( frame ) {
|
if ( frame ) {
|
||||||
av_frame_free(&frame);
|
av_frame_free(&frame);
|
||||||
frame = zm_av_frame_alloc();
|
frame = zm_av_frame_alloc();
|
||||||
} else {
|
} else {
|
||||||
frame = zm_av_frame_alloc();
|
frame = zm_av_frame_alloc();
|
||||||
}
|
}
|
||||||
//Debug(1,"Getting frame %d", streams[packet.stream_index].frame_count);
|
ret = zm_receive_frame(context, frame, packet);
|
||||||
ret = avcodec_receive_frame(context, frame);
|
|
||||||
if ( ret < 0 ) {
|
if ( ret < 0 ) {
|
||||||
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
|
Error("Unable to decode frame at frame %d: %s, continuing",
|
||||||
Error( "Unable to send packet at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
|
streams[packet.stream_index].frame_count, av_make_error_string(ret).c_str());
|
||||||
zm_av_packet_unref( &packet );
|
zm_av_packet_unref(&packet);
|
||||||
av_frame_free(&frame);
|
av_frame_free(&frame);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if HAVE_AVUTIL_HWCONTEXT_H
|
frameComplete = 1;
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
frameComplete = 1;
|
|
||||||
# else
|
|
||||||
if ( frame ) {
|
|
||||||
av_frame_free(&frame);
|
|
||||||
frame = zm_av_frame_alloc();
|
|
||||||
} else {
|
|
||||||
frame = zm_av_frame_alloc();
|
|
||||||
}
|
|
||||||
ret = zm_avcodec_decode_video(context, frame, &frameComplete, &packet);
|
|
||||||
if ( ret < 0 ) {
|
|
||||||
av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
|
|
||||||
Error( "Unable to decode frame at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
|
|
||||||
zm_av_packet_unref( &packet );
|
|
||||||
av_frame_free(&frame);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
Debug(1, "Success getting a packet at frame (%d)", streams[packet.stream_index].frame_count);
|
|
||||||
streams[packet.stream_index].frame_count += 1;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
zm_av_packet_unref(&packet);
|
zm_av_packet_unref(&packet);
|
||||||
|
|
||||||
|
@ -236,7 +182,7 @@ AVFrame *FFmpeg_Input::get_frame( int stream_id ) {
|
||||||
return frame;
|
return frame;
|
||||||
} // end AVFrame *FFmpeg_Input::get_frame
|
} // end AVFrame *FFmpeg_Input::get_frame
|
||||||
|
|
||||||
AVFrame *FFmpeg_Input::get_frame( int stream_id, double at ) {
|
AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) {
|
||||||
Debug(1, "Getting frame from stream %d at %f", stream_id, at);
|
Debug(1, "Getting frame from stream %d at %f", stream_id, at);
|
||||||
|
|
||||||
int64_t seek_target = (int64_t)(at * AV_TIME_BASE);
|
int64_t seek_target = (int64_t)(at * AV_TIME_BASE);
|
||||||
|
@ -248,9 +194,8 @@ AVFrame *FFmpeg_Input::get_frame( int stream_id, double at ) {
|
||||||
|
|
||||||
if ( !frame ) {
|
if ( !frame ) {
|
||||||
// Don't have a frame yet, so get a keyframe before the timestamp
|
// Don't have a frame yet, so get a keyframe before the timestamp
|
||||||
if ( ( ret = av_seek_frame(
|
ret = av_seek_frame(input_format_context, stream_id, seek_target, AVSEEK_FLAG_FRAME);
|
||||||
input_format_context, stream_id, seek_target, AVSEEK_FLAG_FRAME
|
if ( ret < 0 ) {
|
||||||
) < 0 ) ) {
|
|
||||||
Error("Unable to seek in stream");
|
Error("Unable to seek in stream");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -276,7 +221,7 @@ AVFrame *FFmpeg_Input::get_frame( int stream_id, double at ) {
|
||||||
if ( frame->pts <= seek_target ) {
|
if ( frame->pts <= seek_target ) {
|
||||||
zm_dump_frame(frame, "pts <= seek_target");
|
zm_dump_frame(frame, "pts <= seek_target");
|
||||||
while ( frame && (frame->pts < seek_target) ) {
|
while ( frame && (frame->pts < seek_target) ) {
|
||||||
if ( ! get_frame(stream_id) )
|
if ( !get_frame(stream_id) )
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
return frame;
|
return frame;
|
||||||
|
|
|
@ -0,0 +1,263 @@
|
||||||
|
//
|
||||||
|
// ZoneMinder Fifo Debug
|
||||||
|
// Copyright (C) 2019 ZoneMinder LLC
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// of the License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/file.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
#include "zm.h"
|
||||||
|
#include "zm_time.h"
|
||||||
|
#include "zm_signal.h"
|
||||||
|
#include "zm_monitor.h"
|
||||||
|
#include "zm_fifo.h"
|
||||||
|
#define RAW_BUFFER 512
|
||||||
|
static bool zm_fifodbg_inited = false;
|
||||||
|
FILE *zm_fifodbg_log_fd = 0;
|
||||||
|
char zm_fifodbg_log[PATH_MAX] = "";
|
||||||
|
|
||||||
|
static bool zmFifoDbgOpen() {
|
||||||
|
if ( zm_fifodbg_log_fd )
|
||||||
|
fclose(zm_fifodbg_log_fd);
|
||||||
|
zm_fifodbg_log_fd = NULL;
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
FifoStream::fifo_create_if_missing(zm_fifodbg_log);
|
||||||
|
int fd = open(zm_fifodbg_log, O_WRONLY|O_NONBLOCK|O_TRUNC);
|
||||||
|
if ( fd < 0 )
|
||||||
|
return false;
|
||||||
|
int res = flock(fd, LOCK_EX | LOCK_NB);
|
||||||
|
if ( res < 0 ) {
|
||||||
|
close(fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
zm_fifodbg_log_fd = fdopen(fd, "wb");
|
||||||
|
if ( zm_fifodbg_log_fd == NULL ) {
|
||||||
|
close(fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int zmFifoDbgInit(Monitor *monitor) {
|
||||||
|
zm_fifodbg_inited = true;
|
||||||
|
snprintf(zm_fifodbg_log, sizeof(zm_fifodbg_log), "%s/%d/dbgpipe.log",
|
||||||
|
monitor->getStorage()->Path(), monitor->Id());
|
||||||
|
zmFifoDbgOpen();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void zmFifoDbgOutput(
|
||||||
|
int hex,
|
||||||
|
const char * const file,
|
||||||
|
const int line,
|
||||||
|
const int level,
|
||||||
|
const char *fstring,
|
||||||
|
...
|
||||||
|
) {
|
||||||
|
char dbg_string[8192];
|
||||||
|
int str_size = sizeof(dbg_string);
|
||||||
|
|
||||||
|
va_list arg_ptr;
|
||||||
|
if ( (!zm_fifodbg_inited) || ( !zm_fifodbg_log_fd && !zmFifoDbgOpen() ) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
char *dbg_ptr = dbg_string;
|
||||||
|
va_start(arg_ptr, fstring);
|
||||||
|
if ( hex ) {
|
||||||
|
unsigned char *data = va_arg(arg_ptr, unsigned char *);
|
||||||
|
int len = va_arg(arg_ptr, int);
|
||||||
|
dbg_ptr += snprintf(dbg_ptr, str_size-(dbg_ptr-dbg_string), "%d:", len);
|
||||||
|
for ( int i = 0; i < len; i++ ) {
|
||||||
|
dbg_ptr += snprintf(dbg_ptr, str_size-(dbg_ptr-dbg_string), " %02x", data[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dbg_ptr += vsnprintf(dbg_ptr, str_size-(dbg_ptr-dbg_string), fstring, arg_ptr);
|
||||||
|
}
|
||||||
|
va_end(arg_ptr);
|
||||||
|
strncpy(dbg_ptr++, "\n", 2);
|
||||||
|
int res = fwrite(dbg_string, dbg_ptr-dbg_string, 1, zm_fifodbg_log_fd);
|
||||||
|
if ( res != 1 ) {
|
||||||
|
fclose(zm_fifodbg_log_fd);
|
||||||
|
zm_fifodbg_log_fd = NULL;
|
||||||
|
} else {
|
||||||
|
fflush(zm_fifodbg_log_fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FifoStream::sendRAWFrames() {
|
||||||
|
static unsigned char buffer[RAW_BUFFER];
|
||||||
|
int fd = open(stream_path, O_RDONLY);
|
||||||
|
if ( fd < 0 ) {
|
||||||
|
Error("Can't open %s: %s", stream_path, strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
while ( (bytes_read = read(fd, buffer, RAW_BUFFER)) ) {
|
||||||
|
if ( bytes_read == 0 )
|
||||||
|
continue;
|
||||||
|
if ( bytes_read < 0 ) {
|
||||||
|
Error("Problem during reading: %s", strerror(errno));
|
||||||
|
close(fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ( fwrite(buffer, bytes_read, 1, stdout) != 1 ) {
|
||||||
|
Error("Problem during writing: %s", strerror(errno));
|
||||||
|
close(fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FifoStream::file_create_if_missing(
|
||||||
|
const char * path,
|
||||||
|
bool is_fifo,
|
||||||
|
bool delete_fake_fifo
|
||||||
|
) {
|
||||||
|
static struct stat st;
|
||||||
|
if ( stat(path, &st) == 0 ) {
|
||||||
|
if ( (!is_fifo) || S_ISFIFO(st.st_mode) || !delete_fake_fifo )
|
||||||
|
return;
|
||||||
|
Debug(5, "Supposed to be a fifo pipe but isn't, unlinking: %s", path);
|
||||||
|
unlink(path);
|
||||||
|
}
|
||||||
|
int fd;
|
||||||
|
if ( !is_fifo ) {
|
||||||
|
Debug(5, "Creating non fifo file as requested: %s", path);
|
||||||
|
fd = open(path, O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR);
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Debug(5, "Making fifo file of: %s", path);
|
||||||
|
mkfifo(path, S_IRUSR|S_IWUSR);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FifoStream::fifo_create_if_missing(
|
||||||
|
const char * path,
|
||||||
|
bool delete_fake_fifo
|
||||||
|
) {
|
||||||
|
file_create_if_missing(path, true, delete_fake_fifo);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FifoStream::sendMJEGFrames() {
|
||||||
|
static unsigned char buffer[ZM_MAX_IMAGE_SIZE];
|
||||||
|
int fd = open(stream_path, O_RDONLY);
|
||||||
|
if ( fd < 0 ) {
|
||||||
|
Error("Can't open %s: %s", stream_path, strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
total_read = 0;
|
||||||
|
while (
|
||||||
|
(bytes_read = read(fd, buffer+total_read, ZM_MAX_IMAGE_SIZE-total_read))
|
||||||
|
) {
|
||||||
|
if ( bytes_read < 0 ) {
|
||||||
|
Error("Problem during reading: %s", strerror(errno));
|
||||||
|
close(fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
total_read += bytes_read;
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
if ( (total_read == 0) || (frame_count%frame_mod != 0) )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if ( fprintf(stdout,
|
||||||
|
"--ZoneMinderFrame\r\n"
|
||||||
|
"Content-Type: image/jpeg\r\n"
|
||||||
|
"Content-Length: %d\r\n\r\n",
|
||||||
|
total_read) < 0 ) {
|
||||||
|
Error("Problem during writing: %s", strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( fwrite(buffer, total_read, 1, stdout) != 1 ) {
|
||||||
|
Error("Problem during reading: %s", strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fprintf(stdout, "\r\n\r\n");
|
||||||
|
fflush(stdout);
|
||||||
|
last_frame_sent = TV_2_FLOAT(now);
|
||||||
|
frame_count++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FifoStream::setStreamStart(const char * path) {
|
||||||
|
stream_path = strdup(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FifoStream::setStreamStart(int monitor_id, const char * format) {
|
||||||
|
char diag_path[PATH_MAX];
|
||||||
|
const char * filename;
|
||||||
|
Monitor * monitor = Monitor::Load(monitor_id, false, Monitor::QUERY);
|
||||||
|
|
||||||
|
if ( !strcmp(format, "reference") ) {
|
||||||
|
stream_type = MJPEG;
|
||||||
|
filename = "diagpipe-r.jpg";
|
||||||
|
} else if ( !strcmp(format, "delta") ) {
|
||||||
|
filename = "diagpipe-d.jpg";
|
||||||
|
stream_type = MJPEG;
|
||||||
|
} else {
|
||||||
|
stream_type = RAW;
|
||||||
|
filename = "dbgpipe.log";
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(diag_path, sizeof(diag_path), "%s/%d/%s",
|
||||||
|
monitor->getStorage()->Path(), monitor->Id(), filename);
|
||||||
|
setStreamStart(diag_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FifoStream::runStream() {
|
||||||
|
if ( stream_type == MJPEG ) {
|
||||||
|
fprintf(stdout, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n");
|
||||||
|
} else {
|
||||||
|
fprintf(stdout, "Content-Type: text/html\r\n\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
char lock_file[PATH_MAX];
|
||||||
|
snprintf(lock_file, sizeof(lock_file), "%s.rlock", stream_path);
|
||||||
|
file_create_if_missing(lock_file, false);
|
||||||
|
|
||||||
|
int fd_lock = open(lock_file, O_RDONLY);
|
||||||
|
if ( fd_lock < 0 ) {
|
||||||
|
Error("Can't open %s: %s", lock_file, strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int res = flock(fd_lock, LOCK_EX | LOCK_NB);
|
||||||
|
if ( res < 0 ) {
|
||||||
|
Error("Flocking problem on %s: - %s", lock_file, strerror(errno));
|
||||||
|
close(fd_lock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ( !zm_terminate ) {
|
||||||
|
gettimeofday(&now, NULL);
|
||||||
|
checkCommandQueue();
|
||||||
|
if ( stream_type == MJPEG ) {
|
||||||
|
if ( !sendMJEGFrames() )
|
||||||
|
zm_terminate = true;
|
||||||
|
} else {
|
||||||
|
if ( !sendRAWFrames() )
|
||||||
|
zm_terminate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(fd_lock);
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
//
|
||||||
|
// ZoneMinder Fifo Debug
|
||||||
|
// Copyright (C) 2019 ZoneMinder LLC
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// of the License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
//
|
||||||
|
#ifndef ZM_FIFO_H
|
||||||
|
#define ZM_FIFO_H
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include "zm.h"
|
||||||
|
#include "zm_image.h"
|
||||||
|
#endif
|
||||||
|
#include "zm_monitor.h"
|
||||||
|
#include "zm_stream.h"
|
||||||
|
|
||||||
|
#define zmFifoDbgPrintf(level, params...) {\
|
||||||
|
zmFifoDbgOutput(0, __FILE__, __LINE__, level, ##params);\
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef ZM_DBG_OFF
|
||||||
|
#define FifoDebug(level, params...) zmFifoDbgPrintf(level, ##params)
|
||||||
|
#else
|
||||||
|
#define FifoDebug(level, params...)
|
||||||
|
#endif
|
||||||
|
void zmFifoDbgOutput(
|
||||||
|
int hex,
|
||||||
|
const char * const file,
|
||||||
|
const int line,
|
||||||
|
const int level,
|
||||||
|
const char *fstring,
|
||||||
|
...) __attribute__((format(printf, 5, 6)));
|
||||||
|
int zmFifoDbgInit(Monitor * monitor);
|
||||||
|
|
||||||
|
class FifoStream : public StreamBase {
|
||||||
|
private:
|
||||||
|
char * stream_path;
|
||||||
|
int fd;
|
||||||
|
int total_read;
|
||||||
|
int bytes_read;
|
||||||
|
unsigned int frame_count;
|
||||||
|
static void file_create_if_missing(
|
||||||
|
const char * path,
|
||||||
|
bool is_fifo,
|
||||||
|
bool delete_fake_fifo = true
|
||||||
|
);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
typedef enum { MJPEG, RAW } StreamType;
|
||||||
|
StreamType stream_type;
|
||||||
|
bool sendMJEGFrames();
|
||||||
|
bool sendRAWFrames();
|
||||||
|
void processCommand(const CmdMsg *msg) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
FifoStream() {}
|
||||||
|
static void fifo_create_if_missing(
|
||||||
|
const char * path,
|
||||||
|
bool delete_fake_fifo = true);
|
||||||
|
void setStreamStart(const char * path);
|
||||||
|
void setStreamStart(int monitor_id, const char * format);
|
||||||
|
void runStream();
|
||||||
|
};
|
||||||
|
#endif // ZM_FIFO_H
|
|
@ -46,7 +46,7 @@ Group::Group(unsigned int p_id) {
|
||||||
|
|
||||||
if ( p_id ) {
|
if ( p_id ) {
|
||||||
char sql[ZM_SQL_SML_BUFSIZ];
|
char sql[ZM_SQL_SML_BUFSIZ];
|
||||||
snprintf(sql, sizeof(sql), "SELECT Id, ParentId, Name FROM Group WHERE Id=%d", p_id);
|
snprintf(sql, sizeof(sql), "SELECT `Id`, `ParentId`, `Name` FROM `Group` WHERE `Id`=%d", p_id);
|
||||||
Debug(2,"Loading Group for %d using %s", p_id, sql);
|
Debug(2,"Loading Group for %d using %s", p_id, sql);
|
||||||
zmDbRow dbrow;
|
zmDbRow dbrow;
|
||||||
if ( !dbrow.fetch(sql) ) {
|
if ( !dbrow.fetch(sql) ) {
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "zm_rgb.h"
|
#include "zm_rgb.h"
|
||||||
#include "zm_ffmpeg.h"
|
#include "zm_ffmpeg.h"
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
|
@ -504,8 +505,8 @@ uint8_t* Image::WriteBuffer(const unsigned int p_width, const unsigned int p_hei
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !p_height || !p_width ) {
|
if ( ! ( p_height > 0 && p_width > 0 ) ) {
|
||||||
Error("WriteBuffer called with invalid width or height: %d %d",p_width,p_height);
|
Error("WriteBuffer called with invalid width or height: %d %d", p_width, p_height);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -532,11 +533,10 @@ uint8_t* Image::WriteBuffer(const unsigned int p_width, const unsigned int p_hei
|
||||||
colours = p_colours;
|
colours = p_colours;
|
||||||
subpixelorder = p_subpixelorder;
|
subpixelorder = p_subpixelorder;
|
||||||
pixels = height*width;
|
pixels = height*width;
|
||||||
size = newsize;
|
size = newsize;
|
||||||
}
|
} // end if need to re-alloc buffer
|
||||||
|
|
||||||
return buffer;
|
return buffer;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Assign an existing buffer to the image instead of copying from a source buffer. The goal is to reduce the amount of memory copying and increase efficiency and buffer reusing. */
|
/* Assign an existing buffer to the image instead of copying from a source buffer. The goal is to reduce the amount of memory copying and increase efficiency and buffer reusing. */
|
||||||
|
@ -967,7 +967,7 @@ cinfo->out_color_space = JCS_RGB;
|
||||||
return( true );
|
return( true );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multiple calling formats to permit inclusion (or not) of both quality_override and timestamp (exif), with suitable defaults.
|
// Multiple calling formats to permit inclusion (or not) of non blocking, quality_override and timestamp (exif), with suitable defaults.
|
||||||
// Note quality=zero means default
|
// Note quality=zero means default
|
||||||
|
|
||||||
bool Image::WriteJpeg(const char *filename, int quality_override) const {
|
bool Image::WriteJpeg(const char *filename, int quality_override) const {
|
||||||
|
@ -976,33 +976,71 @@ bool Image::WriteJpeg(const char *filename, int quality_override) const {
|
||||||
bool Image::WriteJpeg(const char *filename) const {
|
bool Image::WriteJpeg(const char *filename) const {
|
||||||
return Image::WriteJpeg(filename, 0, (timeval){0,0});
|
return Image::WriteJpeg(filename, 0, (timeval){0,0});
|
||||||
}
|
}
|
||||||
|
bool Image::WriteJpeg(const char *filename, bool on_blocking_abort) const {
|
||||||
|
return Image::WriteJpeg(filename, 0, (timeval){0,0}, on_blocking_abort);
|
||||||
|
}
|
||||||
bool Image::WriteJpeg(const char *filename, struct timeval timestamp) const {
|
bool Image::WriteJpeg(const char *filename, struct timeval timestamp) const {
|
||||||
return Image::WriteJpeg(filename, 0, timestamp);
|
return Image::WriteJpeg(filename, 0, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Image::WriteJpeg(const char *filename, int quality_override, struct timeval timestamp) const {
|
bool Image::WriteJpeg(const char *filename, int quality_override, struct timeval timestamp) const {
|
||||||
|
return Image::WriteJpeg(filename, quality_override, timestamp, false);
|
||||||
|
}
|
||||||
|
bool Image::WriteJpeg(const char *filename, int quality_override, struct timeval timestamp, bool on_blocking_abort) const {
|
||||||
if ( config.colour_jpeg_files && colours == ZM_COLOUR_GRAY8 ) {
|
if ( config.colour_jpeg_files && colours == ZM_COLOUR_GRAY8 ) {
|
||||||
Image temp_image(*this);
|
Image temp_image(*this);
|
||||||
temp_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB);
|
temp_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB);
|
||||||
return temp_image.WriteJpeg(filename, quality_override, timestamp);
|
return temp_image.WriteJpeg(filename, quality_override, timestamp, on_blocking_abort);
|
||||||
}
|
}
|
||||||
int quality = quality_override?quality_override:config.jpeg_file_quality;
|
int quality = quality_override?quality_override:config.jpeg_file_quality;
|
||||||
|
|
||||||
struct jpeg_compress_struct *cinfo = writejpg_ccinfo[quality];
|
struct jpeg_compress_struct *cinfo = writejpg_ccinfo[quality];
|
||||||
|
FILE *outfile =NULL;
|
||||||
|
static int raw_fd = 0;
|
||||||
|
bool need_create_comp = false;
|
||||||
|
raw_fd = 0;
|
||||||
|
|
||||||
if ( !cinfo ) {
|
if ( !cinfo ) {
|
||||||
cinfo = writejpg_ccinfo[quality] = new jpeg_compress_struct;
|
cinfo = writejpg_ccinfo[quality] = new jpeg_compress_struct;
|
||||||
cinfo->err = jpeg_std_error( &jpg_err.pub );
|
cinfo->err = jpeg_std_error( &jpg_err.pub );
|
||||||
jpg_err.pub.error_exit = zm_jpeg_error_exit;
|
|
||||||
jpg_err.pub.emit_message = zm_jpeg_emit_message;
|
|
||||||
jpeg_create_compress( cinfo );
|
jpeg_create_compress( cinfo );
|
||||||
|
need_create_comp=true;
|
||||||
|
}
|
||||||
|
if (! on_blocking_abort) {
|
||||||
|
jpg_err.pub.error_exit = zm_jpeg_error_exit;
|
||||||
|
jpg_err.pub.emit_message = zm_jpeg_emit_message;
|
||||||
|
} else {
|
||||||
|
jpg_err.pub.error_exit = zm_jpeg_error_silent;
|
||||||
|
jpg_err.pub.emit_message = zm_jpeg_emit_silence;
|
||||||
|
if (setjmp( jpg_err.setjmp_buffer ) ) {
|
||||||
|
jpeg_abort_compress( cinfo );
|
||||||
|
Debug( 5, "Aborted a write mid-stream and %s and %d", (outfile == NULL) ? "closing file" : "file not opened", raw_fd );
|
||||||
|
if (raw_fd)
|
||||||
|
close(raw_fd);
|
||||||
|
if (outfile)
|
||||||
|
fclose( outfile );
|
||||||
|
return ( false );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (need_create_comp)
|
||||||
|
jpeg_create_compress( cinfo );
|
||||||
|
|
||||||
|
if (! on_blocking_abort) {
|
||||||
|
if ( (outfile = fopen(filename, "wb")) == NULL ) {
|
||||||
|
Error( "Can't open %s for writing: %s", filename, strerror(errno) );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
raw_fd = open(filename,O_WRONLY|O_NONBLOCK|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
|
||||||
|
if (raw_fd < 0)
|
||||||
|
return ( false );
|
||||||
|
outfile = fdopen(raw_fd,"wb");
|
||||||
|
if (outfile == NULL) {
|
||||||
|
close(raw_fd);
|
||||||
|
return( false );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FILE *outfile;
|
|
||||||
if ( (outfile = fopen(filename, "wb")) == NULL ) {
|
|
||||||
Error("Can't open %s: %s", filename, strerror(errno));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
jpeg_stdio_dest( cinfo, outfile );
|
jpeg_stdio_dest( cinfo, outfile );
|
||||||
|
|
||||||
cinfo->image_width = width; /* image width and height, in pixels */
|
cinfo->image_width = width; /* image width and height, in pixels */
|
||||||
|
|
|
@ -56,9 +56,9 @@ extern imgbufcpy_fptr_t fptr_imgbufcpy;
|
||||||
|
|
||||||
/* Should be called from Image class functions */
|
/* Should be called from Image class functions */
|
||||||
inline static uint8_t* AllocBuffer(size_t p_bufsize) {
|
inline static uint8_t* AllocBuffer(size_t p_bufsize) {
|
||||||
uint8_t* buffer = (uint8_t*)zm_mallocaligned(64,p_bufsize);
|
uint8_t* buffer = (uint8_t*)zm_mallocaligned(64, p_bufsize);
|
||||||
if ( buffer == NULL )
|
if ( buffer == NULL )
|
||||||
Fatal("Memory allocation failed: %s",strerror(errno));
|
Fatal("Memory allocation failed: %s", strerror(errno));
|
||||||
|
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ inline static void DumpBuffer(uint8_t* buffer, int buffertype) {
|
||||||
av_free(buffer);
|
av_free(buffer);
|
||||||
*/
|
*/
|
||||||
} else {
|
} else {
|
||||||
Error( "Unknown buffer type in DumpBuffer(%d)", buffertype );
|
Error("Unknown buffer type in DumpBuffer(%d)", buffertype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,9 +240,12 @@ public:
|
||||||
bool ReadJpeg( const char *filename, unsigned int p_colours, unsigned int p_subpixelorder);
|
bool ReadJpeg( const char *filename, unsigned int p_colours, unsigned int p_subpixelorder);
|
||||||
|
|
||||||
bool WriteJpeg ( const char *filename) const;
|
bool WriteJpeg ( const char *filename) const;
|
||||||
|
bool WriteJpeg ( const char *filename, bool on_blocking_abort) const;
|
||||||
bool WriteJpeg ( const char *filename, int quality_override ) const;
|
bool WriteJpeg ( const char *filename, int quality_override ) const;
|
||||||
bool WriteJpeg ( const char *filename, struct timeval timestamp ) const;
|
bool WriteJpeg ( const char *filename, struct timeval timestamp ) const;
|
||||||
bool WriteJpeg ( const char *filename, int quality_override, struct timeval timestamp ) const;
|
bool WriteJpeg ( const char *filename, int quality_override, struct timeval timestamp ) const;
|
||||||
|
bool WriteJpeg ( const char *filename, int quality_override, struct timeval timestamp, bool on_blocking_abort ) const;
|
||||||
|
|
||||||
|
|
||||||
bool DecodeJpeg( const JOCTET *inbuffer, int inbuffer_size, unsigned int p_colours, unsigned int p_subpixelorder);
|
bool DecodeJpeg( const JOCTET *inbuffer, int inbuffer_size, unsigned int p_colours, unsigned int p_subpixelorder);
|
||||||
bool EncodeJpeg( JOCTET *outbuffer, int *outbuffer_size, int quality_override=0 ) const;
|
bool EncodeJpeg( JOCTET *outbuffer, int *outbuffer_size, int quality_override=0 ) const;
|
||||||
|
|
|
@ -30,6 +30,13 @@ extern "C"
|
||||||
|
|
||||||
static int jpeg_err_count = 0;
|
static int jpeg_err_count = 0;
|
||||||
|
|
||||||
|
void zm_jpeg_error_silent( j_common_ptr cinfo ){
|
||||||
|
zm_error_ptr zmerr = (zm_error_ptr)cinfo->err;
|
||||||
|
longjmp( zmerr->setjmp_buffer, 1 );
|
||||||
|
}
|
||||||
|
void zm_jpeg_emit_silence( j_common_ptr cinfo, int msg_level ){
|
||||||
|
}
|
||||||
|
|
||||||
void zm_jpeg_error_exit( j_common_ptr cinfo )
|
void zm_jpeg_error_exit( j_common_ptr cinfo )
|
||||||
{
|
{
|
||||||
static char buffer[JMSG_LENGTH_MAX];
|
static char buffer[JMSG_LENGTH_MAX];
|
||||||
|
|
|
@ -38,6 +38,8 @@ struct zm_error_mgr
|
||||||
|
|
||||||
typedef struct zm_error_mgr *zm_error_ptr;
|
typedef struct zm_error_mgr *zm_error_ptr;
|
||||||
|
|
||||||
|
void zm_jpeg_error_silent( j_common_ptr cinfo );
|
||||||
|
void zm_jpeg_emit_silence( j_common_ptr cinfo, int msg_level );
|
||||||
void zm_jpeg_error_exit( j_common_ptr cinfo );
|
void zm_jpeg_error_exit( j_common_ptr cinfo );
|
||||||
void zm_jpeg_emit_message( j_common_ptr cinfo, int msg_level );
|
void zm_jpeg_emit_message( j_common_ptr cinfo, int msg_level );
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
#if HAVE_LIBAVFORMAT
|
#if HAVE_LIBAVFORMAT
|
||||||
#include "zm_ffmpeg_camera.h"
|
#include "zm_ffmpeg_camera.h"
|
||||||
#endif // HAVE_LIBAVFORMAT
|
#endif // HAVE_LIBAVFORMAT
|
||||||
|
#include "zm_fifo.h"
|
||||||
#if HAVE_LIBVLC
|
#if HAVE_LIBVLC
|
||||||
#include "zm_libvlc_camera.h"
|
#include "zm_libvlc_camera.h"
|
||||||
#endif // HAVE_LIBVLC
|
#endif // HAVE_LIBVLC
|
||||||
|
@ -67,18 +68,19 @@
|
||||||
// This is the official SQL (and ordering of the fields) to load a Monitor.
|
// This is the official SQL (and ordering of the fields) to load a Monitor.
|
||||||
// It will be used whereever a Monitor dbrow is needed. WHERE conditions can be appended
|
// It will be used whereever a Monitor dbrow is needed. WHERE conditions can be appended
|
||||||
std::string load_monitor_sql =
|
std::string load_monitor_sql =
|
||||||
"SELECT Id, Name, ServerId, StorageId, Type, Function+0, Enabled, LinkedMonitors, "
|
"SELECT `Id`, `Name`, `ServerId`, `StorageId`, `Type`, `Function`+0, `Enabled`, `LinkedMonitors`, "
|
||||||
"AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS,"
|
"`AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`,"
|
||||||
"Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, " // V4L Settings
|
"`Device`, `Channel`, `Format`, `V4LMultiBuffer`, `V4LCapturesPerFrame`, " // V4L Settings
|
||||||
"Protocol, Method, Options, User, Pass, Host, Port, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, "
|
"`Protocol`, `Method`, `Options`, `User`, `Pass`, `Host`, `Port`, `Path`, `Width`, `Height`, `Colours`, `Palette`, `Orientation`+0, `Deinterlacing`, "
|
||||||
"SaveJPEGs, VideoWriter, EncoderParameters, "
|
"`DecoderHWAccelName`, `DecoderHWAccelDevice`, `RTSPDescribe`, "
|
||||||
"OutputCodec, Encoder, OutputContainer, "
|
"`SaveJPEGs`, `VideoWriter`, `EncoderParameters`, "
|
||||||
"RecordAudio, "
|
"`OutputCodec`, `Encoder`, `OutputContainer`, "
|
||||||
"Brightness, Contrast, Hue, Colour, "
|
"`RecordAudio`, "
|
||||||
"EventPrefix, LabelFormat, LabelX, LabelY, LabelSize,"
|
"`Brightness`, `Contrast`, `Hue`, `Colour`, "
|
||||||
"ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, "
|
"`EventPrefix`, `LabelFormat`, `LabelX`, `LabelY`, `LabelSize`,"
|
||||||
"SectionLength, FrameSkip, MotionFrameSkip, "
|
"`ImageBufferCount`, `WarmupCount`, `PreEventCount`, `PostEventCount`, `StreamReplayBuffer`, `AlarmFrameCount`, "
|
||||||
"FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif, SignalCheckPoints, SignalCheckColour FROM Monitors";
|
"`SectionLength`, `MinSectionLength`, `FrameSkip`, `MotionFrameSkip`, "
|
||||||
|
"`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`, `SignalCheckPoints`, `SignalCheckColour` FROM `Monitors`";
|
||||||
|
|
||||||
std::string CameraType_Strings[] = {
|
std::string CameraType_Strings[] = {
|
||||||
"Local",
|
"Local",
|
||||||
|
@ -306,6 +308,7 @@ Monitor::Monitor()
|
||||||
post_event_count(0),
|
post_event_count(0),
|
||||||
stream_replay_buffer(0),
|
stream_replay_buffer(0),
|
||||||
section_length(0),
|
section_length(0),
|
||||||
|
min_section_length(0),
|
||||||
frame_skip(0),
|
frame_skip(0),
|
||||||
motion_frame_skip(0),
|
motion_frame_skip(0),
|
||||||
analysis_fps_limit(0),
|
analysis_fps_limit(0),
|
||||||
|
@ -454,6 +457,9 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
|
||||||
deinterlacing = atoi(dbrow[col]); col++;
|
deinterlacing = atoi(dbrow[col]); col++;
|
||||||
deinterlacing_value = deinterlacing & 0xff;
|
deinterlacing_value = deinterlacing & 0xff;
|
||||||
|
|
||||||
|
decoder_hwaccel_name = dbrow[col] ? dbrow[col] : ""; col++;
|
||||||
|
decoder_hwaccel_device = dbrow[col] ? dbrow[col] : ""; col++;
|
||||||
|
|
||||||
rtsp_describe = (dbrow[col] && *dbrow[col] != '0'); col++;
|
rtsp_describe = (dbrow[col] && *dbrow[col] != '0'); col++;
|
||||||
|
|
||||||
savejpegs = atoi(dbrow[col]); col++;
|
savejpegs = atoi(dbrow[col]); col++;
|
||||||
|
@ -546,8 +552,12 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
|
||||||
+ (image_buffer_count * width * height * colours)
|
+ (image_buffer_count * width * height * colours)
|
||||||
+ 64; /* Padding used to permit aligning the images buffer to 64 byte boundary */
|
+ 64; /* Padding used to permit aligning the images buffer to 64 byte boundary */
|
||||||
|
|
||||||
Debug(1, "mem.size SharedData=%d TriggerData=%d VideoStoreData=%d total=%" PRId64,
|
Debug(1, "mem.size(%d) SharedData=%d TriggerData=%d VideoStoreData=%d timestamps=%d images=%dx%d = %" PRId64 " total=%" PRId64,
|
||||||
sizeof(SharedData), sizeof(TriggerData), sizeof(VideoStoreData), mem_size);
|
sizeof(mem_size),
|
||||||
|
sizeof(SharedData), sizeof(TriggerData), sizeof(VideoStoreData),
|
||||||
|
(image_buffer_count*sizeof(struct timeval)),
|
||||||
|
image_buffer_count, camera->ImageSize(), (image_buffer_count*camera->ImageSize()),
|
||||||
|
mem_size);
|
||||||
mem_ptr = NULL;
|
mem_ptr = NULL;
|
||||||
|
|
||||||
Zone **zones = 0;
|
Zone **zones = 0;
|
||||||
|
@ -678,7 +688,9 @@ Camera * Monitor::getCamera() {
|
||||||
hue,
|
hue,
|
||||||
colour,
|
colour,
|
||||||
purpose==CAPTURE,
|
purpose==CAPTURE,
|
||||||
record_audio
|
record_audio,
|
||||||
|
decoder_hwaccel_name,
|
||||||
|
decoder_hwaccel_device
|
||||||
);
|
);
|
||||||
#endif // HAVE_LIBAVFORMAT
|
#endif // HAVE_LIBAVFORMAT
|
||||||
} else if ( type == NVSOCKET ) {
|
} else if ( type == NVSOCKET ) {
|
||||||
|
@ -766,6 +778,14 @@ Monitor *Monitor::Load(unsigned int p_id, bool load_zones, Purpose purpose) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ( config.record_diag_images ) {
|
||||||
|
diag_path_r = stringtf(config.record_diag_images_fifo ? "%s/%d/diagpipe-r.jpg" : "%s/%d/diag-r.jpg", storage->Path(), id);
|
||||||
|
diag_path_d = stringtf(config.record_diag_images_fifo ? "%s/%d/diagpipe-d.jpg" : "%s/%d/diag-d.jpg", storage->Path(), id);
|
||||||
|
if (config.record_diag_images_fifo){
|
||||||
|
FifoStream::fifo_create_if_missing(diag_path_r.c_str());
|
||||||
|
FifoStream::fifo_create_if_missing(diag_path_d.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return monitor;
|
return monitor;
|
||||||
|
@ -833,7 +853,7 @@ bool Monitor::connect() {
|
||||||
if ( shm_id < 0 ) {
|
if ( shm_id < 0 ) {
|
||||||
Fatal("Can't shmget, probably not enough shared memory space free: %s", strerror(errno));
|
Fatal("Can't shmget, probably not enough shared memory space free: %s", strerror(errno));
|
||||||
}
|
}
|
||||||
mem_ptr = (unsigned char *)shmat( shm_id, 0, 0 );
|
mem_ptr = (unsigned char *)shmat(shm_id, 0, 0);
|
||||||
if ( mem_ptr < (void *)0 ) {
|
if ( mem_ptr < (void *)0 ) {
|
||||||
Fatal("Can't shmat: %s", strerror(errno));
|
Fatal("Can't shmat: %s", strerror(errno));
|
||||||
}
|
}
|
||||||
|
@ -1180,17 +1200,19 @@ double Monitor::GetFPS() const {
|
||||||
|
|
||||||
double time_diff = tvDiffSec( time2, time1 );
|
double time_diff = tvDiffSec( time2, time1 );
|
||||||
if ( ! time_diff ) {
|
if ( ! time_diff ) {
|
||||||
Error( "No diff between time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d", time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count );
|
Error("No diff between time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d",
|
||||||
|
time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count);
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
double curr_fps = fps_image_count/time_diff;
|
double curr_fps = fps_image_count/time_diff;
|
||||||
|
|
||||||
if ( curr_fps < 0.0 ) {
|
if ( curr_fps < 0.0 ) {
|
||||||
Error( "Negative FPS %f, time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d",
|
Error("Negative FPS %f, time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d",
|
||||||
curr_fps, time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count );
|
curr_fps, time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count);
|
||||||
return 0.0;
|
return 0.0;
|
||||||
} else {
|
} else {
|
||||||
Debug( 2, "GetFPS %f, time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d", curr_fps, time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count );
|
Debug(2, "GetFPS %f, time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d",
|
||||||
|
curr_fps, time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count);
|
||||||
}
|
}
|
||||||
return curr_fps;
|
return curr_fps;
|
||||||
}
|
}
|
||||||
|
@ -1418,7 +1440,7 @@ void Monitor::DumpZoneImage(const char *zone_string) {
|
||||||
} else {
|
} else {
|
||||||
Debug(3, "Trying to load from event");
|
Debug(3, "Trying to load from event");
|
||||||
// Grab the most revent event image
|
// Grab the most revent event image
|
||||||
std::string sql = stringtf("SELECT MAX(Id) FROM Events WHERE MonitorId=%d AND Frames > 0", id);
|
std::string sql = stringtf("SELECT MAX(`Id`) FROM `Events` WHERE `MonitorId`=%d AND `Frames` > 0", id);
|
||||||
zmDbRow eventid_row;
|
zmDbRow eventid_row;
|
||||||
if ( eventid_row.fetch(sql.c_str()) ) {
|
if ( eventid_row.fetch(sql.c_str()) ) {
|
||||||
uint64_t event_id = atoll(eventid_row[0]);
|
uint64_t event_id = atoll(eventid_row[0]);
|
||||||
|
@ -1633,7 +1655,7 @@ void Monitor::UpdateAnalysisFPS() {
|
||||||
bool Monitor::Analyse() {
|
bool Monitor::Analyse() {
|
||||||
// last_write_index is the last capture
|
// last_write_index is the last capture
|
||||||
// last_read_index is the last analysis
|
// last_read_index is the last analysis
|
||||||
|
|
||||||
if ( !Enabled() ) {
|
if ( !Enabled() ) {
|
||||||
Warning("Shouldn't be doing Analyze when not Enabled");
|
Warning("Shouldn't be doing Analyze when not Enabled");
|
||||||
return false;
|
return false;
|
||||||
|
@ -1682,13 +1704,15 @@ bool Monitor::Analyse() {
|
||||||
// Specifically told to be on. Setting the score here will trigger the alarm.
|
// Specifically told to be on. Setting the score here will trigger the alarm.
|
||||||
if ( trigger_data->trigger_state == TRIGGER_ON ) {
|
if ( trigger_data->trigger_state == TRIGGER_ON ) {
|
||||||
score += trigger_data->trigger_score;
|
score += trigger_data->trigger_score;
|
||||||
|
Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score);
|
||||||
if ( !event ) {
|
if ( !event ) {
|
||||||
if ( cause.length() )
|
// How could it have a length already?
|
||||||
cause += ", ";
|
//if ( cause.length() )
|
||||||
|
//cause += ", ";
|
||||||
cause += trigger_data->trigger_cause;
|
cause += trigger_data->trigger_cause;
|
||||||
}
|
}
|
||||||
Event::StringSet noteSet;
|
Event::StringSet noteSet;
|
||||||
noteSet.insert( trigger_data->trigger_text );
|
noteSet.insert(trigger_data->trigger_text);
|
||||||
noteSetMap[trigger_data->trigger_cause] = noteSet;
|
noteSetMap[trigger_data->trigger_cause] = noteSet;
|
||||||
} // end if trigger_on
|
} // end if trigger_on
|
||||||
|
|
||||||
|
@ -1716,7 +1740,7 @@ bool Monitor::Analyse() {
|
||||||
cause += SIGNAL_CAUSE;
|
cause += SIGNAL_CAUSE;
|
||||||
}
|
}
|
||||||
Event::StringSet noteSet;
|
Event::StringSet noteSet;
|
||||||
noteSet.insert( signalText );
|
noteSet.insert(signalText);
|
||||||
noteSetMap[SIGNAL_CAUSE] = noteSet;
|
noteSetMap[SIGNAL_CAUSE] = noteSet;
|
||||||
shared_data->state = state = IDLE;
|
shared_data->state = state = IDLE;
|
||||||
shared_data->active = signal;
|
shared_data->active = signal;
|
||||||
|
@ -1752,22 +1776,29 @@ bool Monitor::Analyse() {
|
||||||
}
|
}
|
||||||
noteSetMap[MOTION_CAUSE] = zoneSet;
|
noteSetMap[MOTION_CAUSE] = zoneSet;
|
||||||
} // end if motion_score
|
} // end if motion_score
|
||||||
shared_data->active = signal;
|
//shared_data->active = signal; // unneccessary active gets set on signal change
|
||||||
} // if ( Active() && (function == MODECT || function == MOCORD) )
|
} // end if active and doing motion detection
|
||||||
|
|
||||||
// If we aren't recording, check linked monitors to see if we should be.
|
|
||||||
|
// Check to see if linked monitors are triggering.
|
||||||
if ( n_linked_monitors > 0 ) {
|
if ( n_linked_monitors > 0 ) {
|
||||||
Debug(2, "Checking linked monitors");
|
// FIXME improve logic here
|
||||||
|
bool first_link = true;
|
||||||
Event::StringSet noteSet;
|
Event::StringSet noteSet;
|
||||||
for ( int i=0; i < n_linked_monitors; i++ ) {
|
for ( int i = 0; i < n_linked_monitors; i++ ) {
|
||||||
if ( ! linked_monitors[i]->isConnected() )
|
// TODO: Shouldn't we try to connect?
|
||||||
linked_monitors[i]->connect();
|
if ( linked_monitors[i]->isConnected() ) {
|
||||||
|
if ( linked_monitors[i]->hasAlarmed() ) {
|
||||||
if ( linked_monitors[i]->isConnected() && linked_monitors[i]->hasAlarmed() ) {
|
if ( !event ) {
|
||||||
if ( !event ) {
|
if ( first_link ) {
|
||||||
if ( cause.length() )
|
if ( cause.length() )
|
||||||
cause += ", ";
|
cause += ", ";
|
||||||
cause += LINKED_CAUSE;
|
cause += LINKED_CAUSE;
|
||||||
|
first_link = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
noteSet.insert(linked_monitors[i]->Name());
|
||||||
|
score += 50;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
linked_monitors[i]->connect();
|
linked_monitors[i]->connect();
|
||||||
|
@ -1798,7 +1829,7 @@ bool Monitor::Analyse() {
|
||||||
} // end if event
|
} // end if event
|
||||||
|
|
||||||
if ( !event ) {
|
if ( !event ) {
|
||||||
Debug(2,"Creating continuous event");
|
Debug(2, "Creating continuous event");
|
||||||
// Create event
|
// Create event
|
||||||
event = new Event(this, *timestamp, "Continuous", noteSetMap);
|
event = new Event(this, *timestamp, "Continuous", noteSetMap);
|
||||||
shared_data->last_event_id = event->Id();
|
shared_data->last_event_id = event->Id();
|
||||||
|
@ -1806,13 +1837,13 @@ bool Monitor::Analyse() {
|
||||||
// lets construct alarm cause. It will contain cause + names of zones alarmed
|
// lets construct alarm cause. It will contain cause + names of zones alarmed
|
||||||
std::string alarm_cause = "Continuous";
|
std::string alarm_cause = "Continuous";
|
||||||
for ( int i=0; i < n_zones; i++ ) {
|
for ( int i=0; i < n_zones; i++ ) {
|
||||||
if (zones[i]->Alarmed()) {
|
if ( zones[i]->Alarmed() ) {
|
||||||
alarm_cause += std::string(zones[i]->Label());
|
alarm_cause += std::string(zones[i]->Label());
|
||||||
if ( i < n_zones-1 ) {
|
if ( i < n_zones-1 ) {
|
||||||
alarm_cause +=",";
|
alarm_cause += ",";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
alarm_cause = cause+" "+alarm_cause;
|
alarm_cause = cause+" "+alarm_cause;
|
||||||
strncpy(shared_data->alarm_cause,alarm_cause.c_str() , sizeof(shared_data->alarm_cause));
|
strncpy(shared_data->alarm_cause,alarm_cause.c_str() , sizeof(shared_data->alarm_cause));
|
||||||
video_store_data->recording = event->StartTime();
|
video_store_data->recording = event->StartTime();
|
||||||
|
@ -1826,26 +1857,52 @@ bool Monitor::Analyse() {
|
||||||
} // end if RECORDING
|
} // end if RECORDING
|
||||||
|
|
||||||
if ( score ) {
|
if ( score ) {
|
||||||
Debug(9, "Score: (%d)", score);
|
if ( (state == IDLE) || (state == TAPE) || (state == PREALARM) ) {
|
||||||
if ( state == IDLE || state == TAPE || state == PREALARM ) {
|
// If we should end then previous continuous event and start a new non-continuous event
|
||||||
if ( Event::PreAlarmCount() >= (alarm_frame_count-1) ) {
|
if ( event && event->Frames()
|
||||||
Info("%s: %03d - Gone into alarm state", name, analysis_image_count);
|
&& (!event->AlarmFrames())
|
||||||
|
&& (event_close_mode == CLOSE_ALARM)
|
||||||
|
&& ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= min_section_length )
|
||||||
|
) {
|
||||||
|
Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins",
|
||||||
|
name, image_count, event->Id());
|
||||||
|
closeEvent();
|
||||||
|
} else if ( event ) {
|
||||||
|
// This is so if we need more than 1 alarm frame before going into alarm, so it is basically if we have enough alarm frames
|
||||||
|
Debug(3, "pre-alarm-count in event %d, event frames %d, alarm frames %d event length %d >=? %d",
|
||||||
|
Event::PreAlarmCount(), event->Frames(), event->AlarmFrames(),
|
||||||
|
( timestamp->tv_sec - video_store_data->recording.tv_sec ), min_section_length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( (!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1) ) {
|
||||||
shared_data->state = state = ALARM;
|
shared_data->state = state = ALARM;
|
||||||
|
// lets construct alarm cause. It will contain cause + names of zones alarmed
|
||||||
|
std::string alarm_cause = "";
|
||||||
|
for ( int i=0; i < n_zones; i++ ) {
|
||||||
|
if ( zones[i]->Alarmed() ) {
|
||||||
|
alarm_cause = alarm_cause + "," + std::string(zones[i]->Label());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( !alarm_cause.empty() ) alarm_cause[0] = ' ';
|
||||||
|
alarm_cause = cause + alarm_cause;
|
||||||
|
strncpy(shared_data->alarm_cause, alarm_cause.c_str(), sizeof(shared_data->alarm_cause)-1);
|
||||||
|
Info("%s: %03d - Gone into alarm state PreAlarmCount: %u > AlarmFrameCount:%u Cause:%s",
|
||||||
|
name, image_count, Event::PreAlarmCount(), alarm_frame_count, shared_data->alarm_cause);
|
||||||
|
|
||||||
if ( !event ) {
|
if ( !event ) {
|
||||||
// lets construct alarm cause. It will contain cause + names of zones alarmed
|
|
||||||
std::string alarm_cause = "";
|
|
||||||
for ( int i=0; i < n_zones; i++) {
|
|
||||||
if (zones[i]->Alarmed()) {
|
|
||||||
alarm_cause += std::string(zones[i]->Label());
|
|
||||||
if (i < n_zones-1) {
|
|
||||||
alarm_cause +=",";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
alarm_cause = cause+" "+alarm_cause;
|
|
||||||
strncpy(shared_data->alarm_cause,alarm_cause.c_str() , sizeof(shared_data->alarm_cause));
|
|
||||||
event = new Event(this, *timestamp, cause, noteSetMap);
|
event = new Event(this, *timestamp, cause, noteSetMap);
|
||||||
|
|
||||||
shared_data->last_event_id = event->Id();
|
shared_data->last_event_id = event->Id();
|
||||||
|
//set up video store data
|
||||||
|
snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile());
|
||||||
|
video_store_data->recording = event->StartTime();
|
||||||
|
|
||||||
|
Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name, image_count, event->Id());
|
||||||
|
|
||||||
|
}
|
||||||
|
if ( alarm_frame_count ) {
|
||||||
|
event->SavePreAlarmFrames();
|
||||||
}
|
}
|
||||||
} else if ( state != PREALARM ) {
|
} else if ( state != PREALARM ) {
|
||||||
Info("%s: %03d - Gone into prealarm state", name, analysis_image_count);
|
Info("%s: %03d - Gone into prealarm state", name, analysis_image_count);
|
||||||
|
@ -1896,10 +1953,9 @@ bool Monitor::Analyse() {
|
||||||
anal_image->Overlay( *(zones[i]->AlarmImage()) );
|
anal_image->Overlay( *(zones[i]->AlarmImage()) );
|
||||||
got_anal_image = true;
|
got_anal_image = true;
|
||||||
}
|
}
|
||||||
if ( config.record_event_stats || state == ALARM ) {
|
if ( config.record_event_stats && (state == ALARM) )
|
||||||
zones[i]->RecordStats( event );
|
zones[i]->RecordStats(event);
|
||||||
}
|
} // end if zone is alarmed
|
||||||
}
|
|
||||||
} // end foreach zone
|
} // end foreach zone
|
||||||
if ( got_anal_image ) {
|
if ( got_anal_image ) {
|
||||||
snap->analysis_image = anal_image;
|
snap->analysis_image = anal_image;
|
||||||
|
@ -1908,8 +1964,9 @@ bool Monitor::Analyse() {
|
||||||
}
|
}
|
||||||
} else if ( config.record_event_stats && state == ALARM ) {
|
} else if ( config.record_event_stats && state == ALARM ) {
|
||||||
for ( int i = 0; i < n_zones; i++ ) {
|
for ( int i = 0; i < n_zones; i++ ) {
|
||||||
if ( zones[i]->Alarmed() )
|
if ( zones[i]->Alarmed() ) {
|
||||||
zones[i]->RecordStats(event);
|
zones[i]->RecordStats(event);
|
||||||
|
}
|
||||||
} // end foreach zone
|
} // end foreach zone
|
||||||
} // analsys_images or record stats
|
} // analsys_images or record stats
|
||||||
|
|
||||||
|
@ -1919,19 +1976,19 @@ bool Monitor::Analyse() {
|
||||||
// Alert means this frame has no motion, but we were alarmed and are still recording.
|
// Alert means this frame has no motion, but we were alarmed and are still recording.
|
||||||
if ( noteSetMap.size() > 0 )
|
if ( noteSetMap.size() > 0 )
|
||||||
event->updateNotes( noteSetMap );
|
event->updateNotes( noteSetMap );
|
||||||
//} else if ( state == TAPE ) {
|
//} else if ( state == TAPE ) {
|
||||||
//if ( !(analysis_image_count%(frame_skip+1)) ) {
|
//if ( !(analysis_image_count%(frame_skip+1)) ) {
|
||||||
//if ( config.bulk_frame_interval > 1 ) {
|
//if ( config.bulk_frame_interval > 1 ) {
|
||||||
//event->AddFrame( snap_image, *timestamp, (event->Frames()<pre_event_count?0:-1) );
|
//event->AddFrame( snap_image, *timestamp, (event->Frames()<pre_event_count?0:-1) );
|
||||||
//} else {
|
//} else {
|
||||||
//event->AddFrame( snap_image, *timestamp );
|
//event->AddFrame( snap_image, *timestamp );
|
||||||
//}
|
|
||||||
//}
|
//}
|
||||||
}
|
//}
|
||||||
if ( function == MODECT || function == MOCORD ) {
|
}
|
||||||
ref_image.Blend( *snap_image, ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc ) );
|
if ( function == MODECT || function == MOCORD ) {
|
||||||
}
|
ref_image.Blend( *snap_image, ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc ) );
|
||||||
last_signal = signal;
|
}
|
||||||
|
last_signal = signal;
|
||||||
} // end if signal
|
} // end if signal
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -2005,7 +2062,7 @@ void Monitor::Reload() {
|
||||||
if ( !row ) {
|
if ( !row ) {
|
||||||
Error("Can't run query: %s", mysql_error(&dbconn));
|
Error("Can't run query: %s", mysql_error(&dbconn));
|
||||||
} else if ( MYSQL_ROW dbrow = row->mysql_row() ) {
|
} else if ( MYSQL_ROW dbrow = row->mysql_row() ) {
|
||||||
Load(dbrow,1,purpose);
|
Load(dbrow, 1, purpose);
|
||||||
|
|
||||||
shared_data->state = state = IDLE;
|
shared_data->state = state = IDLE;
|
||||||
shared_data->alarm_x = shared_data->alarm_y = -1;
|
shared_data->alarm_x = shared_data->alarm_y = -1;
|
||||||
|
@ -2474,21 +2531,8 @@ unsigned int Monitor::DetectMotion(const Image &comp_image, Event::StringSet &zo
|
||||||
ref_image.Delta(comp_image, &delta_image);
|
ref_image.Delta(comp_image, &delta_image);
|
||||||
|
|
||||||
if ( config.record_diag_images ) {
|
if ( config.record_diag_images ) {
|
||||||
static char diag_path[PATH_MAX] = "";
|
ref_image.WriteJpeg(diag_path_r.c_str(), config.record_diag_images_fifo);
|
||||||
if ( !diag_path[0] ) {
|
delta_image.WriteJpeg(diag_path_d.c_str(), config.record_diag_images_fifo);
|
||||||
snprintf(diag_path, sizeof(diag_path), "%s/%d/diag-r.jpg", storage->Path(), id);
|
|
||||||
}
|
|
||||||
ref_image.WriteJpeg( diag_path );
|
|
||||||
}
|
|
||||||
|
|
||||||
ref_image.Delta(comp_image, &delta_image);
|
|
||||||
|
|
||||||
if ( config.record_diag_images ) {
|
|
||||||
static char diag_path[PATH_MAX] = "";
|
|
||||||
if ( !diag_path[0] ) {
|
|
||||||
snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-d.jpg", storage->Path(), id );
|
|
||||||
}
|
|
||||||
delta_image.WriteJpeg(diag_path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blank out all exclusion zones
|
// Blank out all exclusion zones
|
||||||
|
@ -2669,6 +2713,7 @@ bool Monitor::DumpSettings(char *output, bool verbose) {
|
||||||
sprintf(output+strlen(output), "Stream Replay Buffer : %d\n", stream_replay_buffer );
|
sprintf(output+strlen(output), "Stream Replay Buffer : %d\n", stream_replay_buffer );
|
||||||
sprintf(output+strlen(output), "Alarm Frame Count : %d\n", alarm_frame_count );
|
sprintf(output+strlen(output), "Alarm Frame Count : %d\n", alarm_frame_count );
|
||||||
sprintf(output+strlen(output), "Section Length : %d\n", section_length);
|
sprintf(output+strlen(output), "Section Length : %d\n", section_length);
|
||||||
|
sprintf(output+strlen(output), "Min Section Length : %d\n", min_section_length);
|
||||||
sprintf(output+strlen(output), "Maximum FPS : %.2f\n", capture_delay?(double)DT_PREC_3/capture_delay:0.0);
|
sprintf(output+strlen(output), "Maximum FPS : %.2f\n", capture_delay?(double)DT_PREC_3/capture_delay:0.0);
|
||||||
sprintf(output+strlen(output), "Alarm Maximum FPS : %.2f\n", alarm_capture_delay?(double)DT_PREC_3/alarm_capture_delay:0.0);
|
sprintf(output+strlen(output), "Alarm Maximum FPS : %.2f\n", alarm_capture_delay?(double)DT_PREC_3/alarm_capture_delay:0.0);
|
||||||
sprintf(output+strlen(output), "Reference Blend %%ge : %d\n", ref_blend_perc);
|
sprintf(output+strlen(output), "Reference Blend %%ge : %d\n", ref_blend_perc);
|
||||||
|
@ -2733,8 +2778,8 @@ std::vector<Group *> Monitor::Groups() {
|
||||||
// At the moment, only load groups once.
|
// At the moment, only load groups once.
|
||||||
if ( !groups.size() ) {
|
if ( !groups.size() ) {
|
||||||
std::string sql = stringtf(
|
std::string sql = stringtf(
|
||||||
"SELECT Id,ParentId,Name FROM Groups WHERE Groups.Id IN "
|
"SELECT `Id`, `ParentId`, `Name` FROM `Groups` WHERE `Groups.Id` IN "
|
||||||
"(SELECT GroupId FROM Groups_Monitors WHERE MonitorId=%d)",id);
|
"(SELECT `GroupId` FROM `Groups_Monitors` WHERE `MonitorId`=%d)",id);
|
||||||
MYSQL_RES *result = zmDbFetch(sql.c_str());
|
MYSQL_RES *result = zmDbFetch(sql.c_str());
|
||||||
if ( !result ) {
|
if ( !result ) {
|
||||||
Error("Can't load groups: %s", mysql_error(&dbconn));
|
Error("Can't load groups: %s", mysql_error(&dbconn));
|
||||||
|
|
|
@ -259,6 +259,8 @@ protected:
|
||||||
unsigned int deinterlacing_value;
|
unsigned int deinterlacing_value;
|
||||||
bool videoRecording;
|
bool videoRecording;
|
||||||
bool rtsp_describe;
|
bool rtsp_describe;
|
||||||
|
std::string decoder_hwaccel_name;
|
||||||
|
std::string decoder_hwaccel_device;
|
||||||
|
|
||||||
int savejpegs;
|
int savejpegs;
|
||||||
int colours;
|
int colours;
|
||||||
|
@ -290,6 +292,7 @@ protected:
|
||||||
int post_event_count; // How many unalarmed images must occur before the alarm state is reset
|
int post_event_count; // How many unalarmed images must occur before the alarm state is reset
|
||||||
int stream_replay_buffer; // How many frames to store to support DVR functions, IGNORED from this object, passed directly into zms now
|
int stream_replay_buffer; // How many frames to store to support DVR functions, IGNORED from this object, passed directly into zms now
|
||||||
int section_length; // How long events should last in continuous modes
|
int section_length; // How long events should last in continuous modes
|
||||||
|
int min_section_length; // Minimum event length when using event_close_mode == ALARM
|
||||||
bool adaptive_skip; // Whether to use the newer adaptive algorithm for this monitor
|
bool adaptive_skip; // Whether to use the newer adaptive algorithm for this monitor
|
||||||
int frame_skip; // How many frames to skip in continuous modes
|
int frame_skip; // How many frames to skip in continuous modes
|
||||||
int motion_frame_skip; // How many frames to skip in motion detection
|
int motion_frame_skip; // How many frames to skip in motion detection
|
||||||
|
@ -301,11 +304,13 @@ protected:
|
||||||
int fps_report_interval; // How many images should be captured/processed between reporting the current FPS
|
int fps_report_interval; // How many images should be captured/processed between reporting the current FPS
|
||||||
int ref_blend_perc; // Percentage of new image going into reference image.
|
int ref_blend_perc; // Percentage of new image going into reference image.
|
||||||
int alarm_ref_blend_perc; // Percentage of new image going into reference image during alarm.
|
int alarm_ref_blend_perc; // Percentage of new image going into reference image during alarm.
|
||||||
bool track_motion; // Whether this monitor tries to track detected motion
|
bool track_motion; // Whether this monitor tries to track detected motion
|
||||||
int signal_check_points; // Number of points in the image to check for signal
|
int signal_check_points; // Number of points in the image to check for signal
|
||||||
Rgb signal_check_colour; // The colour that the camera will emit when no video signal detected
|
Rgb signal_check_colour; // The colour that the camera will emit when no video signal detected
|
||||||
bool embed_exif; // Whether to embed Exif data into each image frame or not
|
bool embed_exif; // Whether to embed Exif data into each image frame or not
|
||||||
|
|
||||||
|
int capture_max_fps;
|
||||||
|
|
||||||
double capture_fps; // Current capturing fps
|
double capture_fps; // Current capturing fps
|
||||||
double analysis_fps; // Current analysis fps
|
double analysis_fps; // Current analysis fps
|
||||||
|
|
||||||
|
@ -380,8 +385,6 @@ public:
|
||||||
explicit Monitor();
|
explicit Monitor();
|
||||||
explicit Monitor(int p_id);
|
explicit Monitor(int p_id);
|
||||||
|
|
||||||
// OurCheckAlarms seems to be unused. Check it on zm_monitor.cpp for more info.
|
|
||||||
//bool OurCheckAlarms( Zone *zone, const Image *pImage );
|
|
||||||
~Monitor();
|
~Monitor();
|
||||||
|
|
||||||
void AddZones( int p_n_zones, Zone *p_zones[] );
|
void AddZones( int p_n_zones, Zone *p_zones[] );
|
||||||
|
@ -467,6 +470,7 @@ public:
|
||||||
void UpdateAdaptiveSkip();
|
void UpdateAdaptiveSkip();
|
||||||
useconds_t GetAnalysisRate();
|
useconds_t GetAnalysisRate();
|
||||||
unsigned int GetAnalysisUpdateDelay() const { return analysis_update_delay; }
|
unsigned int GetAnalysisUpdateDelay() const { return analysis_update_delay; }
|
||||||
|
unsigned int GetCaptureMaxFPS() const { return capture_max_fps; }
|
||||||
int GetCaptureDelay() const { return capture_delay; }
|
int GetCaptureDelay() const { return capture_delay; }
|
||||||
int GetAlarmCaptureDelay() const { return alarm_capture_delay; }
|
int GetAlarmCaptureDelay() const { return alarm_capture_delay; }
|
||||||
unsigned int GetLastReadIndex() const;
|
unsigned int GetLastReadIndex() const;
|
||||||
|
|
|
@ -27,6 +27,8 @@
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <glob.h>
|
#include <glob.h>
|
||||||
|
|
||||||
|
const int MAX_SLEEP_USEC=1000000; // 1 sec
|
||||||
|
|
||||||
bool MonitorStream::checkSwapPath(const char *path, bool create_path) {
|
bool MonitorStream::checkSwapPath(const char *path, bool create_path) {
|
||||||
|
|
||||||
struct stat stat_buf;
|
struct stat stat_buf;
|
||||||
|
@ -418,7 +420,7 @@ bool MonitorStream::sendFrame(Image *image, struct timeval *timestamp) {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
fputs("\r\n\r\n",stdout);
|
fputs("\r\n\r\n", stdout);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
||||||
struct timeval frameEndTime;
|
struct timeval frameEndTime;
|
||||||
|
@ -427,7 +429,8 @@ bool MonitorStream::sendFrame(Image *image, struct timeval *timestamp) {
|
||||||
int frameSendTime = tvDiffMsec(frameStartTime, frameEndTime);
|
int frameSendTime = tvDiffMsec(frameStartTime, frameEndTime);
|
||||||
if ( frameSendTime > 1000/maxfps ) {
|
if ( frameSendTime > 1000/maxfps ) {
|
||||||
maxfps /= 1.5;
|
maxfps /= 1.5;
|
||||||
Error("Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps);
|
Warning("Frame send time %d msec too slow, throttling maxfps to %.2f",
|
||||||
|
frameSendTime, maxfps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
last_frame_sent = TV_2_FLOAT(now);
|
last_frame_sent = TV_2_FLOAT(now);
|
||||||
|
@ -519,16 +522,33 @@ void MonitorStream::runStream() {
|
||||||
} // end if connkey & playback_buffer
|
} // end if connkey & playback_buffer
|
||||||
|
|
||||||
float max_secs_since_last_sent_frame = 10.0; //should be > keep alive amount (5 secs)
|
float max_secs_since_last_sent_frame = 10.0; //should be > keep alive amount (5 secs)
|
||||||
|
// if MaxFPS < 0 as in 1/60 = 0.017 then we won't get another frame for 60 sec.
|
||||||
|
double capture_fps = monitor->GetFPS();
|
||||||
|
double capture_max_fps = monitor->GetCaptureMaxFPS();
|
||||||
|
if ( capture_max_fps && ( capture_fps > capture_max_fps ) ) {
|
||||||
|
Debug(1, "Using %.3f for fps instead of current fps %.3f", capture_max_fps, capture_fps);
|
||||||
|
capture_fps = capture_max_fps;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( capture_fps < 1 ) {
|
||||||
|
max_secs_since_last_sent_frame = 10/capture_fps;
|
||||||
|
Debug(1, "Adjusting max_secs_since_last_sent_frame to %.2f from current fps %.2f",
|
||||||
|
max_secs_since_last_sent_frame, monitor->GetFPS());
|
||||||
|
} else {
|
||||||
|
Debug(1, "Not Adjusting max_secs_since_last_sent_frame to %.2f from current fps %.2f",
|
||||||
|
max_secs_since_last_sent_frame, monitor->GetFPS());
|
||||||
|
}
|
||||||
|
|
||||||
while ( !zm_terminate ) {
|
while ( !zm_terminate ) {
|
||||||
bool got_command = false;
|
bool got_command = false;
|
||||||
if ( feof(stdout) ) {
|
if ( feof(stdout) ) {
|
||||||
Debug(2,"feof stdout");
|
Debug(2, "feof stdout");
|
||||||
break;
|
break;
|
||||||
} else if ( ferror(stdout) ) {
|
} else if ( ferror(stdout) ) {
|
||||||
Debug(2,"ferror stdout");
|
Debug(2, "ferror stdout");
|
||||||
break;
|
break;
|
||||||
} else if ( !monitor->ShmValid() ) {
|
} else if ( !monitor->ShmValid() ) {
|
||||||
Debug(2,"monitor not valid.... maybe we should wait until it comes back.");
|
Debug(2, "monitor not valid.... maybe we should wait until it comes back.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -551,12 +571,12 @@ void MonitorStream::runStream() {
|
||||||
if ( paused ) {
|
if ( paused ) {
|
||||||
if ( !was_paused ) {
|
if ( !was_paused ) {
|
||||||
int index = monitor->shared_data->last_write_index % monitor->image_buffer_count;
|
int index = monitor->shared_data->last_write_index % monitor->image_buffer_count;
|
||||||
Debug(1,"Saving paused image from index %d",index);
|
Debug(1, "Saving paused image from index %d",index);
|
||||||
paused_image = new Image( *monitor->image_buffer[index].image );
|
paused_image = new Image(*monitor->image_buffer[index].image);
|
||||||
paused_timestamp = *(monitor->image_buffer[index].timestamp);
|
paused_timestamp = *(monitor->image_buffer[index].timestamp);
|
||||||
}
|
}
|
||||||
} else if ( paused_image ) {
|
} else if ( paused_image ) {
|
||||||
Debug(1,"Clearing paused_image");
|
Debug(1, "Clearing paused_image");
|
||||||
delete paused_image;
|
delete paused_image;
|
||||||
paused_image = NULL;
|
paused_image = NULL;
|
||||||
}
|
}
|
||||||
|
@ -564,7 +584,7 @@ void MonitorStream::runStream() {
|
||||||
if ( buffered_playback && delayed ) {
|
if ( buffered_playback && delayed ) {
|
||||||
if ( temp_read_index == temp_write_index ) {
|
if ( temp_read_index == temp_write_index ) {
|
||||||
// Go back to live viewing
|
// Go back to live viewing
|
||||||
Debug( 1, "Exceeded temporary streaming buffer" );
|
Debug(1, "Exceeded temporary streaming buffer");
|
||||||
// Clear paused flag
|
// Clear paused flag
|
||||||
paused = false;
|
paused = false;
|
||||||
// Clear delayed_play flag
|
// Clear delayed_play flag
|
||||||
|
@ -615,10 +635,10 @@ void MonitorStream::runStream() {
|
||||||
//paused?
|
//paused?
|
||||||
int temp_index = MOD_ADD(temp_read_index, 0, temp_image_buffer_count);
|
int temp_index = MOD_ADD(temp_read_index, 0, temp_image_buffer_count);
|
||||||
|
|
||||||
double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent;
|
double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent;
|
||||||
if ( got_command || actual_delta_time > 5 ) {
|
if ( got_command || actual_delta_time > 5 ) {
|
||||||
// Send keepalive
|
// Send keepalive
|
||||||
Debug( 2, "Sending keepalive frame %d", temp_index );
|
Debug(2, "Sending keepalive frame %d", temp_index);
|
||||||
// Send the next frame
|
// Send the next frame
|
||||||
if ( !sendFrame( temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp ) )
|
if ( !sendFrame( temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp ) )
|
||||||
zm_terminate = true;
|
zm_terminate = true;
|
||||||
|
@ -629,7 +649,7 @@ void MonitorStream::runStream() {
|
||||||
|
|
||||||
if ( temp_read_index == temp_write_index ) {
|
if ( temp_read_index == temp_write_index ) {
|
||||||
// Go back to live viewing
|
// Go back to live viewing
|
||||||
Warning( "Rewound over write index, resuming live play" );
|
Warning("Rewound over write index, resuming live play");
|
||||||
// Clear paused flag
|
// Clear paused flag
|
||||||
paused = false;
|
paused = false;
|
||||||
// Clear delayed_play flag
|
// Clear delayed_play flag
|
||||||
|
@ -644,7 +664,8 @@ void MonitorStream::runStream() {
|
||||||
if ( tvCmp(last_frame_time, *(snap->timestamp)) ) {
|
if ( tvCmp(last_frame_time, *(snap->timestamp)) ) {
|
||||||
|
|
||||||
last_read_index = monitor->shared_data->last_write_index;
|
last_read_index = monitor->shared_data->last_write_index;
|
||||||
Debug( 2, "index: %d: frame_mod: %d frame count: %d paused(%d) delayed(%d)", index, frame_mod, frame_count, paused, delayed );
|
Debug(2, "index: %d: frame_mod: %d frame count: %d paused(%d) delayed(%d)",
|
||||||
|
index, frame_mod, frame_count, paused, delayed );
|
||||||
if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) {
|
if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) {
|
||||||
if ( !paused && !delayed ) {
|
if ( !paused && !delayed ) {
|
||||||
// Send the next frame
|
// Send the next frame
|
||||||
|
@ -663,18 +684,19 @@ void MonitorStream::runStream() {
|
||||||
|
|
||||||
temp_read_index = temp_write_index;
|
temp_read_index = temp_write_index;
|
||||||
} else {
|
} else {
|
||||||
|
Debug(2, "Paused %d, delayed %d", paused, delayed);
|
||||||
double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent;
|
double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent;
|
||||||
if ( actual_delta_time > 5 ) {
|
if ( actual_delta_time > 5 ) {
|
||||||
if ( paused_image ) {
|
if ( paused_image ) {
|
||||||
// Send keepalive
|
// Send keepalive
|
||||||
Debug(2, "Sending keepalive frame ");
|
Debug(2, "Sending keepalive frame because delta time %.2f > 5", actual_delta_time);
|
||||||
// Send the next frame
|
// Send the next frame
|
||||||
if ( !sendFrame(paused_image, &paused_timestamp) )
|
if ( !sendFrame(paused_image, &paused_timestamp) )
|
||||||
zm_terminate = true;
|
zm_terminate = true;
|
||||||
} else {
|
} else {
|
||||||
Debug(2, "Would have sent keepalive frame, but had no paused_image ");
|
Debug(2, "Would have sent keepalive frame, but had no paused_image ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // end if should send frame
|
} // end if should send frame
|
||||||
|
|
||||||
|
@ -706,11 +728,17 @@ void MonitorStream::runStream() {
|
||||||
} // end if buffered playback
|
} // end if buffered playback
|
||||||
frame_count++;
|
frame_count++;
|
||||||
} else {
|
} else {
|
||||||
Debug(4,"Waiting for capture last_write_index=%u", monitor->shared_data->last_write_index);
|
Debug(3, "Waiting for capture last_write_index=%u", monitor->shared_data->last_write_index);
|
||||||
} // end if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index )
|
} // end if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index )
|
||||||
|
|
||||||
unsigned long sleep_time = (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2)));
|
unsigned long sleep_time = (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2)));
|
||||||
Debug(4, "Sleeping for (%d)", sleep_time);
|
Debug(3, "Sleeping for (%d)", sleep_time);
|
||||||
|
|
||||||
|
if ( sleep_time > MAX_SLEEP_USEC ) {
|
||||||
|
// Shouldn't sleep for long because we need to check command queue, etc.
|
||||||
|
sleep_time = MAX_SLEEP_USEC;
|
||||||
|
}
|
||||||
|
Debug(3, "Sleeping for %dus", sleep_time);
|
||||||
usleep(sleep_time);
|
usleep(sleep_time);
|
||||||
if ( ttl ) {
|
if ( ttl ) {
|
||||||
if ( (now.tv_sec - stream_start_time) > ttl ) {
|
if ( (now.tv_sec - stream_start_time) > ttl ) {
|
||||||
|
@ -721,9 +749,15 @@ void MonitorStream::runStream() {
|
||||||
if ( ! last_frame_sent ) {
|
if ( ! last_frame_sent ) {
|
||||||
// If we didn't capture above, because frame_mod was bad? Then last_frame_sent will not have a value.
|
// If we didn't capture above, because frame_mod was bad? Then last_frame_sent will not have a value.
|
||||||
last_frame_sent = now.tv_sec;
|
last_frame_sent = now.tv_sec;
|
||||||
Warning( "no last_frame_sent. Shouldn't happen. frame_mod was (%d) frame_count (%d) ", frame_mod, frame_count );
|
Warning("no last_frame_sent. Shouldn't happen. frame_mod was (%d) frame_count (%d) ",
|
||||||
} else if ( (!paused) && ( (TV_2_FLOAT( now ) - last_frame_sent) > max_secs_since_last_sent_frame ) ) {
|
frame_mod, frame_count);
|
||||||
Error( "Terminating, last frame sent time %f secs more than maximum of %f", TV_2_FLOAT( now ) - last_frame_sent, max_secs_since_last_sent_frame );
|
} else if (
|
||||||
|
(!paused)
|
||||||
|
&&
|
||||||
|
( (TV_2_FLOAT(now) - last_frame_sent) > max_secs_since_last_sent_frame )
|
||||||
|
) {
|
||||||
|
Error("Terminating, last frame sent time %f secs more than maximum of %f",
|
||||||
|
TV_2_FLOAT(now) - last_frame_sent, max_secs_since_last_sent_frame);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} // end while
|
} // end while
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "zm_ffmpeg.h"
|
#include "zm_ffmpeg.h"
|
||||||
#include "zm_signal.h"
|
#include "zm_signal.h"
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
#include "zm_time.h"
|
||||||
|
|
||||||
zm_packetqueue::zm_packetqueue( int video_image_count, int p_video_stream_id, int p_audio_stream_id ) {
|
zm_packetqueue::zm_packetqueue( int video_image_count, int p_video_stream_id, int p_audio_stream_id ) {
|
||||||
video_stream_id = p_video_stream_id;
|
video_stream_id = p_video_stream_id;
|
||||||
|
@ -189,9 +190,9 @@ unsigned int zm_packetqueue::clearQueue(unsigned int frames_to_keep, int stream_
|
||||||
av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), zm_packet->image_index, frames_to_keep );
|
av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), zm_packet->image_index, frames_to_keep );
|
||||||
|
|
||||||
// Want frames_to_keep video keyframes. Otherwise, we may not have enough
|
// Want frames_to_keep video keyframes. Otherwise, we may not have enough
|
||||||
if ( ( av_packet->stream_index == stream_id) && ( av_packet->flags & AV_PKT_FLAG_KEY ) ) {
|
if ( (av_packet->stream_index == stream_id) && (av_packet->flags & AV_PKT_FLAG_KEY) ) {
|
||||||
Debug(4, "Found keyframe at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)",
|
Debug(4, "Found keyframe at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)",
|
||||||
av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep );
|
av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
packets_to_delete--;
|
packets_to_delete--;
|
||||||
|
@ -314,3 +315,12 @@ bool zm_packetqueue::increment_analysis_it( ) {
|
||||||
analysis_it = next_it;
|
analysis_it = next_it;
|
||||||
return true;
|
return true;
|
||||||
} // end bool zm_packetqueue::increment_analysis_it( )
|
} // end bool zm_packetqueue::increment_analysis_it( )
|
||||||
|
|
||||||
|
void zm_packetqueue::dumpQueue() {
|
||||||
|
std::list<ZMPacket *>::reverse_iterator it;
|
||||||
|
for ( it = pktQueue.rbegin(); it != pktQueue.rend(); ++ it ) {
|
||||||
|
ZMPacket *zm_packet = *it;
|
||||||
|
AVPacket *av_packet = &(zm_packet->packet);
|
||||||
|
dumpPacket(av_packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue