Skip to content

Instantly share code, notes, and snippets.

@667bdrm
Last active September 10, 2022 16:54
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save 667bdrm/209bf33b2d04b08bb318 to your computer and use it in GitHub Desktop.
Save 667bdrm/209bf33b2d04b08bb318 to your computer and use it in GitHub Desktop.
Simple clock synchronization for some chinese DVRs supporting CMS with json-like protocol. Includes some other API commands. Fork at https://gitlab.com/667bdrm/sofiactl
#!/usr/bin/perl
# latest release at https://gitlab.com/667bdrm/sofiactl
#
# Simple clock synchronization for some chinese HiSilicon based DVRs supporting CMS (Sofia software) with json-like protocol. Tested with:
#
# HJCCTV HJ-H4808BW (XiongMai, Hi3520, MBD6304T)
# http://www.aliexpress.com/item/Hybird-NVR-8chs-H-264DVR-8chs-onvif-2-3-Economical-DVR-8ch-Video-4-AUDIO-AND/1918734952.html
#
#
# PBFZ TCV-UTH200 (XiongMai, Hi3518, 50H20L_S39)
# http://www.aliexpress.com/item/Free-shipping-2014-NEW-IP-camera-CCTV-2-0MP-HD-1080P-IP-Network-Security-CCTV-Waterproof/1958962188.html
#
# Additional protocol reference : https://github.com/charmyin/IPCTimeLapse
# vendor sdk: https://github.com/mondwan/cpp-surveillance-cli
# vendor: http://www.xiongmaitech.com
# vendor specifications: http://wiki.xm030.com:81/
package IPcam;
use Module::Load::Conditional qw[can_load check_install requires];
my $use_list = {
'IO::Socket' => undef,
'IO::Socket::INET' => undef,
'Time::Local' => undef,
JSON => undef,
'Data::Dumper' => undef,
};
if (!can_load( modules => $use_list, autoload => true )) {
die('Failed to load required modules: ' . join(', ', keys %{$use_list}));
}
use constant {
LOGIN_REQ1 => 999,
LOGIN_REQ2 => 1000,
LOGIN_RSP => 1000,
LOGOUT_REQ => 1001,
LOGOUT_RSP => 1002,
FORCELOGOUT_REQ => 1003,
FORCELOGOUT_RSP => 1004,
KEEPALIVE_REQ => 1006, # 1005
KEEPALIVE_RSP => 1007, # 1006
SYSINFO_REQ => 1020,
SYSINFO_RSP => 1021,
CONFIG_SET => 1040,
CONFIG_SET_RSP => 1041,
CONFIG_GET => 1042,
CONFIG_GET_RSP => 1043,
DEFAULT_CONFIG_GET => 1044,
DEFAULT_CONFIG_GET_RSP => 1045,
CONFIG_CHANNELTILE_SET => 1046,
CONFIG_CHANNELTILE_SET_RSP => 1047,
CONFIG_CHANNELTILE_GET => 1048,
CONFIG_CHANNELTILE_GET_RSP => 1049,
CONFIG_CHANNELTILE_DOT_SET => 1050,
CONFIG_CHANNELTILE_DOT_SET_RSP => 1051,
SYSTEM_DEBUG_REQ => 1052,
SYSTEM_DEBUG_RSP => 1053,
ABILITY_GET => 1360,
ABILITY_GET_RSP => 1361,
PTZ_REQ => 1400,
PTZ_RSP => 1401,
MONITOR_REQ => 1410,
MONITOR_RSP => 1411,
MONITOR_DATA => 1412,
MONITOR_CLAIM => 1413,
MONITOR_CLAIM_RSP => 1414,
PLAY_REQ => 1420,
PLAY_RSP => 1421,
PLAY_DATA => 1422,
PLAY_EOF => 1423,
PLAY_CLAIM => 1424,
PLAY_CLAIM_RSP => 1425,
DOWNLOAD_DATA => 1426,
TALK_REQ => 1430,
TALK_RSP => 1431,
TALK_CU_PU_DATA => 1432,
TALK_PU_CU_DATA => 1433,
TALK_CLAIM => 1434,
TALK_CLAIM_RSP => 1435,
FILESEARCH_REQ => 1440,
FILESEARCH_RSP => 1441,
LOGSEARCH_REQ => 1442,
LOGSEARCH_RSP => 1443,
FILESEARCH_BYTIME_REQ => 1444,
FILESEARCH_BYTIME_RSP => 1445,
SYSMANAGER_REQ => 1450,
SYSMANAGER_RSP => 1451,
TIMEQUERY_REQ => 1452,
TIMEQUERY_RSP => 1453,
DISKMANAGER_REQ => 1460,
DISKMANAGER_RSP => 1461,
FULLAUTHORITYLIST_GET => 1470,
FULLAUTHORITYLIST_GET_RSP => 1471,
USERS_GET => 1472,
USERS_GET_RSP => 1473,
GROUPS_GET => 1474,
GROUPS_GET_RSP => 1475,
ADDGROUP_REQ => 1476,
ADDGROUP_RSP => 1477,
MODIFYGROUP_REQ => 1478,
MODIFYGROUP_RSP => 1479,
DELETEGROUP_REQ => 1480,
DELETEGROUP_RSP => 1481,
ADDUSER_REQ => 1482,
ADDUSER_RSP => 1483,
MODIFYUSER_REQ => 1484,
MODIFYUSER_RSP => 1485,
DELETEUSER_REQ => 1486,
DELETEUSER_RSP => 1487,
MODIFYPASSWORD_REQ => 1488,
MODIFYPASSWORD_RSP => 1489,
GUARD_REQ => 1500,
GUARD_RSP => 1501,
UNGUARD_REQ => 1502,
UNGUARD_RSP => 1503,
ALARM_REQ => 1504,
ALARM_RSP => 1505,
NET_ALARM_REQ => 1506,
NET_ALARM_REQ => 1507,
ALARMCENTER_MSG_REQ => 1508,
UPGRADE_REQ => 1520,
UPGRADE_RSP => 1521,
UPGRADE_DATA => 1522,
UPGRADE_DATA_RSP => 1523,
UPGRADE_PROGRESS => 1524,
UPGRADE_INFO_REQ => 1525,
UPGRADE_INFO_RSQ => 1526,
IPSEARCH_REQ => 1530,
IPSEARCH_RSP => 1531,
IP_SET_REQ => 1532,
IP_SET_RSP => 1533,
CONFIG_IMPORT_REQ => 1540,
CONFIG_IMPORT_RSP => 1541,
CONFIG_EXPORT_REQ => 1542,
CONFIG_EXPORT_RSP => 1543,
LOG_EXPORT_REQ => 1544, #CONDIG_EXPORT_REQ
LOG_EXPORT_RSP => 1545, #CONFIG_EXPORT_RSP
NET_KEYBOARD_REQ => 1550,
NET_KEYBOARD_RSP => 1551,
NET_SNAP_REQ => 1560,
NET_SNAP_RSP => 1561,
SET_IFRAME_REQ => 1562,
SET_IFRAME_RSP => 1563,
RS232_READ_REQ => 1570,
RS232_READ_RSP => 1571,
RS232_WRITE_REQ => 1572,
RS232_WRITE_RSP => 1573,
RS485_READ_REQ => 1574,
RS485_READ_RSP => 1575,
RS485_WRITE_REQ => 1576,
RS485_WRITE_RSP => 1577,
TRANSPARENT_COMM_REQ => 1578,
TRANSPARENT_COMM_RSP => 1579,
RS485_TRANSPARENT_DATA_REQ => 1580,
RS485_TRANSPARENT_DATA_RSP => 1581,
RS232_TRANSPARENT_DATA_REQ => 1582,
RS232_TRANSPARENT_DATA_RSP => 1583,
SYNC_TIME_REQ => 1590,
SYNC_TIME_RSP => 1591,
PHOTO_GET_REQ => 1600,
PHOTO_GET_RSP => 1601,
};
sub new {
my $classname = shift;
my $self = {};
bless($self, $classname);
$self->_init(@_);
return $self;
}
sub DESTROY {
my $self = shift;
}
sub disconnect {
my $self = shift;
$self->{socket}->close();
}
sub _init {
my $self = shift;
$self->{host} = "";
$self->{port} = 0;
$self->{user} = "";
$self->{password} = "";
$self->{socket} = undef;
$self->{sid} = 0;
$self->{SystemInfo} = undef;
$self->{GenericInfo} = undef;
$self->{lastcommand} = undef;
$self->{sequence} = 0;
if (@_) {
my %extra = @_;
@$self{keys %extra} = values %extra;
}
}
sub getDeviceRuntime {
my $self = shift;
$self->getSystemInfo();
my $total_minutes = hex($self->{SystemInfo}->{DeviceRunTime});
my $total_hours = $total_minutes / 60;
my $total_days = $total_minutes / (60*24);
my $left_minutes = $total_minutes % (60*24);
my $hours = int($left_minutes / 60);
my $minutes = int($left_minutes % 60);
my $years = $total_days / 365;
my $left_days = $total_days % 365;
my $months = int( $left_days / 30);
my $days = $left_days % 30;
$total_minutes -= $months * 24 * 60;
$total_hours = int($total_hours);
$total_days = int($total_days);
$runtime = sprintf("%d day(s): %d year(s), %d month(s), %d day(s), %d hour(s), %d minute(s)", $total_days, $years, $months, $days, $hours, $minutes);
return $runtime;
}
sub BuildPacketSid {
my $self = shift;
return sprintf("0x%08x",$self->{sid});
}
sub BuildPacket {
my $self = shift;
my ($type, $params) = @_;
my @pkt_prefix_1;
my @pkt_prefix_2;
my $pkt_type;
my $json = JSON->new;
@pkt_prefix_1 = (0xff, 0x01, 0x00, 0x00); # (head_flag, version, reserved01, reserved02)
@pkt_prefix_2 = (0x00, 0x00, 0x00, 0x00); # (total_packets, cur_packet)
$pkt_type = $type;
#my $msgid = pack('c*', @$pkt_type);
my $msgid = pack('s', 0) . pack('s', $pkt_type);
my $pkt_prefix_data = pack('c*', @pkt_prefix_1) . pack('i', $self->{sid}) . pack('c*', @pkt_prefix_2). $msgid;
my $pkt_params_data = $json->encode($params);
my $pkt_data = $pkt_prefix_data . pack('i', length($pkt_params_data)) . $pkt_params_data;
$self->{lastcommand} = $params->{Name} . sprintf(" msgid = %d", $pkt_type);
return $pkt_data;
}
sub GetReplyHead {
my $self = shift;
my $data;
my @reply_head_array;
# head_flag, version, reserved
$self->{socket}->recv($data, 4);
my @header = unpack('C*', $data);
my ($head_flag, $version, $reserved01, $reserved02) = (@header)[0,1,2,3];
# int sid, int seq
$self->{socket}->recv($data, 8);
my ($sid,$seq) = unpack('i*', $data);
$reply_head_array[3] = ();
$self->{socket}->recv($data, 8);
my ($channel,$endflag,$msgid,$size) = unpack('CCSI', $data);
my $reply_head = {
Version => $version,
SessionID => $sid,
Sequence => $seq,
MessageId => $msgid,
Content_Length => $size,
};
$self->{sequence} = $reply_head->{Sequence};
printf("reply: head_flag=%x version=%d session=0x%x sequence=%d channel=%d end_flag=%d msgid=%d size = %d lastcommand = %s\n", $head_flag, $version, $sid, $seq, $channel, $end_flag, $msgid, $size, $self->{lastcommand});
return $reply_head;
}
sub GetReplyData {
my $self = shift;
my $reply = $_[0];
my $length = $reply->{'Content_Length'};
my $out;
for (my $downloaded=0; $downloaded < $length; $downloaded++) {
$self->{socket}->recv($data, 1);
$out .= $data;
}
return $out;
}
sub getSystemInfo {
my $self = shift;
if ($self->{SystemInfo} eq undef) {
$self->CmdSystemInfo();
}
return $self->{SystemInfo};
}
sub PrepareGenericCommandHead {
my $self = shift;
my $msgid = $_[0];
my $parameters = $_[1];
my $data;
my $pkt = $parameters;
if ($msgid ne LOGIN_REQ2) {
$parameters->{SessionID} = $self->BuildPacketSid();
}
my $cmd_data = $self->BuildPacket($msgid, $pkt);
$self->{socket}->send($cmd_data);
my $reply_head = $self->GetReplyHead();
return $reply_head;
}
sub PrepareGenericCommand {
my $self = shift;
my $msgid = $_[0];
my $parameters = $_[1];
my $reply_head = $self->PrepareGenericCommandHead($msgid, $parameters);
my $out = $self->GetReplyData($reply_head);
if ($msgid eq LOGIN_REQ2 and exists $reply_head->{SessionID}) {
$self->{sid} = hex($reply_head->{SessionID});
}
if ($out) {
return decode_json($out);
}
return undef;
}
sub PrepareGenericDownloadCommand {
my $self = shift;
my $msgid = $_[0];
my $parameters = $_[1];
my $file = $_[2];
my $reply_head = $self->PrepareGenericCommandHead($msgid, $parameters);
my $out = $self->GetReplyData($reply_head);
open(OUT, ">$file");
print OUT $out;
close(OUT);
return 1;
}
sub CmdLogin {
my $self = shift;
my $data;
my $pkt = {
EncryptType => "MD5",
LoginType => "DVRIP-Web",
PassWord => $self->{password},
UserName => $self->{user}
};
print Dumper $pkt;
$reply_json = $self->PrepareGenericCommand(LOGIN_REQ2, $pkt);
$self->{GenericInfo} = $reply_json;
return $reply_json;
}
sub CmdSystemInfo {
my $self = shift;
my $pkt = {
Name => 'SystemInfo',
};
my $systeminfo = $self->PrepareGenericCommand(SYSINFO_REQ, $pkt);
$self->{SystemInfo} = $systeminfo->{SystemInfo};
return $systeminfo;
}
sub CmdAlarmInfo {
my $self = shift;
my $parameters = $_[0];
my $pkt = {
Name => 'AlarmInfo',
AlarmInfo => $parameters,
};
return $self->PrepareGenericCommand(ALARM_REQ, $pkt);
}
sub CmdOPNetAlarm {
my $self = shift;
my $pkt = {
Name => 'OPNetAlarm',
NetAlarmInfo => {
Event => 0,
State => 1,
},
};
return $self->PrepareGenericCommand(NET_ALARM_REQ, $pkt);
}
sub CmdAlarmCenterMsg {
my $self = shift;
my $data;
my $pkt = {
Name => 'NetAlarmCenter',
NetAlarmCenterMsg => {
Address => "0x0B0A060A",
Channel => 0,
Descrip => "",
Event => "MotionDetect",
SerialID => "003344236523",
StartTime => "2010-06-24 17:04:22",
Status => "Stop",
Type => "Alarm",
},
};
my $cmd_data = $self->BuildPacket(ALARMCENTER_MSG_REQ, $pkt);
$self->{socket}->send($cmd_data);
my $reply_head = $self->GetReplyHead();
my $out = $self->GetReplyData($reply_head);
return decode_json($out);
}
sub CmdOPNetKeyboard {
my $self = shift;
my $parameters = $_[0];
my $pkt = {
Name => 'OPNetKeyboard',
OPNetKeyboard => $parameters,
};
return $self->PrepareGenericCommand(NET_KEYBOARD_REQ, $pkt);
}
sub CmdUsers {
my $self = shift;
my $pkt = {
};
return $self->PrepareGenericCommand(USERS_GET, $pkt);
}
sub CmdGroups {
my $self = shift;
my $pkt = {
};
return $self->PrepareGenericCommand(GROUPS_GET, $pkt);
}
sub CmdStorageInfo {
my $self = shift;
my $pkt = {
Name => 'StorageInfo',
};
return $self->PrepareGenericCommand(SYSINFO_REQ, $pkt);
}
sub CmdWorkState {
my $self = shift;
my $pkt = {
Name => 'WorkState',
};
return $self->PrepareGenericCommand(SYSINFO_REQ, $pkt);
}
sub CmdSnap {
my $self = shift;
my $pkt = {
Name => 'OPSNAP',
};
return $self->PrepareGenericCommand(NET_SNAP_REQ, $pkt);
}
sub CmdEmpty {
my $self = shift;
my $pkt = {
Name => '',
};
return $self->PrepareGenericCommand(SYSINFO_REQ, $pkt);
}
sub CmdKeepAlive {
my $self = shift;
my $pkt = {
Name => 'KeepAlive',
};
return $self->PrepareGenericCommand(KEEPALIVE_REQ, $pkt);
}
sub CmdOPMonitorClaim {
my $self = shift;
my $pkt = {
Name => 'OPMonitor',
SessionID => $self->BuildPacketSid(),
OPMonitor => {
Action => "Claim",
Parameter => {
Channel => 0,
CombinMode => "NONE",
StreamType => "Extra1",
TransMode => "TCP"
}
}
};
my $cmd_data = $self->BuildPacket(MONITOR_CLAIM, $pkt);
$self->{socket}->send($cmd_data);
my $reply = $self->GetReplyHead();
for my $k (keys %{$reply}) {
print "rh = $k\n";
}
my $out = $self->GetReplyData($reply);
my $out1 = decode_json($out);
# $self->{socket}->recv($data, 1);
return $out1;
}
sub CmdOPMonitorStop {
my $self = shift;
my $pkt = {
Name => 'OPMonitor',
SessionID => $self->BuildPacketSid(),
OPMonitor => {
Action => "Stop",
Parameter => {
Channel => 0,
CombinMode => "NONE",
StreamType => "Extra1",
TransMode => "TCP"
}
}
};
my $cmd_data = $self->BuildPacket(MONITOR_REQ, $pkt);
$self->{socket}->send($cmd_data);
my $reply = $self->GetReplyHead();
for my $k (keys %{$reply}) {
print "rh = $k\n";
}
my $out = $self->GetReplyData($reply);
my $out1 = decode_json($out);
# $self->{socket}->recv($data, 1);
return $out1;
}
sub CmdOPMonitorStart {
my $self = shift;
my $data;
my $pkt = {
Name => 'OPMonitor',
SessionID => $self->BuildPacketSid(),
OPMonitor => {
Action => "Start",
Parameter => {
Channel => 0,
CombinMode => "NONE",
StreamType => "Extra1",
TransMode => "TCP"
}
}
};
my $cmd_data = $self->BuildPacket(MONITOR_REQ, $pkt);
$self->{socket}->send($cmd_data);
open(OUT, ">> ".$self->{sid}.".h264");
$stop = 0;
while (defined( my $reply = $self->GetReplyHead()) and $stop == 0 ) {
if (sprintf("%x", $reply->{Data1}) ne "12ff") {
for my $k (keys %{$reply}) {
print "rh = $k\n";
}
print "Content_Length = " . $reply->{Content_Length} . "\n";
my $out = $self->GetReplyData($reply);
print OUT $out;
if ($reply->{Sequence} > 3) {
#$stop = 1;
$self->CmdKeepAlive();
}
} else {
$stop = 1;
break;
}
}
close(OUT);
return $out1;
}
sub CmdOPTimeSetting {
my $self = shift;
my $nortc = $_[0];
my $data;
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
my $clock_cmd = 'OPTimeSetting';
my $pkt_type = SYSMANAGER_REQ;
if ($nortc eq 1) {
$clock_cmd .= 'NoRTC';
$pkt_type = SYNC_TIME_REQ;
}
my $pkt = {
Name => $clock_cmd,
SessionID => $self->BuildPacketSid(),
"$clock_cmd" => sprintf("%4d-%02d-%02d %02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec)
};
my $cmd_data = $self->BuildPacket($pkt_type, $pkt);
$self->{socket}->send($cmd_data);
my $reply = $self->GetReplyHead();
my $out = $self->GetReplyData($reply);
if ($out) {
return decode_json($out);
}
return undef;
}
#buggy
sub CmdSystemFunction {
my $self = shift;
my $pkt = {
Name => 'SystemFunction',
};
return $self->PrepareGenericCommand(ABILITY_REQ, $pkt);
}
sub CmdOPFileQuery {
my $self = shift;
my $parameters = $_[0];
my $pkt = {
Name => 'OPFileQuery',
OPFileQuery => $parameters,
};
return $self->PrepareGenericCommand(FILESEARCH_REQ, $pkt);
}
sub CmdOEMInfo {
my $self = shift;
my $pkt = {
Name => 'OEMInfo',
};
return $self->PrepareGenericCommand(SYSINFO_REQ, $pkt);
}
sub CmdOPPlayBack {
my $self = shift;
my $parameters = $_[0];
my $pkt = {
Name => 'OPPlayBack',
OPPlayBack => $parameters,
};
return $self->PrepareGenericCommand(PLAY_REQ, $pkt);
}
sub CmdOPLogQuery {
my $self = shift;
my $parameters = $_[0];
my $pkt = {
Name => 'OPLogQuery',
OPLogQuery => $parameters,
};
return $self->PrepareGenericCommand(LOGSEARCH_REQ, $pkt);
}
sub LogExport {
my $self = shift;
my $file = $_[0];
my $pkt = {
Name => '',
};
return $self->PrepareGenericDownloadCommand(LOG_EXPORT_REQ, $pkt, $file);
}
sub ConfigExport {
my $self = shift;
my $file = $_[0];
my $pkt = {
Name => '',
};
return $self->PrepareGenericDownloadCommand(CONFIG_EXPORT_REQ, $pkt, $file);
}
sub CmdOPStorageManager {
my $self = shift;
my $data;
my $parameters = $_[0];
my $pkt = {
Name => 'OPStorageManager',
'OPStorageManager' => $parameters,
SessionID => $self->BuildPacketSid(),
};
return $self->PrepareGenericCommand(DISKMANAGER_REQ, $pkt);
}
package main;
use IO::Socket;
use IO::Socket::INET;
use Time::Local;
use Getopt::Long;
use Pod::Usage;
use Data::Dumper;
my $cfgFile = "";
my $cfgUser = "";
my $cfgPass = "";
my $cfgHost = "";
my $cfgPort = "";
my $cfgCmd = undef;
my $help = 0;
my $result = GetOptions (
"help|h" => \$help,
"outputfile|of|o=s" => \$cfgFile,
"user|u=s" => \$cfgUser,
"pass|p=s" => \$cfgPass,
"host|hst=s" => \$cfgHost,
"port|prt=s" => \$cfgPort,
"command|cmd|c=s" => \$cfgCmd,
);
pod2usage(1) if ($help);
if (!($cfgHost or $cfgPort or $cfgUser)) {
print STDERR "You must set user, host and port!\n";
exit(0);
}
my $socket = IO::Socket::INET->new(
PeerAddr => $cfgHost,
PeerPort => $cfgPort,
Proto => 'tcp',
Timeout => 10000,
Type => SOCK_STREAM,
Blocking => 1
) or die "Error at line " . __LINE__. ": $!\n";
print "Setting clock for: host = $cfgHost port = $cfgPort\n";
my $dvr = IPcam->new(host => $cfgHost, port => $cfgPort, user => $cfgUser, password => $cfgPass, socket => $socket);
my $savePath = '/tmp';
my $decoded = $dvr->CmdLogin();
#Ret = 100 - login ok
#Ret = 205 - bad login
#Ret = 101 - ???
$aliveInterval = $decoded->{'AliveInterval'};
print sprintf("SessionID = 0x%08x\n",$dvr->{sid});
print sprintf("AliveInterval = %d\n",$aliveInterval);
my $decoded = $dvr->CmdSystemInfo();
print Dumper $dvr->{GenericInfo};
print Dumper $dvr->getSystemInfo();
print "System running:" . $dvr->getDeviceRuntime() ."\n";
if ($cfgCmd eq "OPTimeSetting") {
$decoded = $dvr->CmdOPTimeSetting(1);
$decoded = $dvr->CmdOPTimeSetting();
} elsif ($cfgCmd eq "Users") {
$decoded = $dvr->CmdUsers();
} elsif ($cfgCmd eq "Groups") {
$decoded = $dvr->CmdGroups();
} elsif ($cfgCmd eq "StorageInfo") {
$decoded = $dvr->CmdStorageInfo();
} elsif ($cfgCmd eq "WorkState") {
$decoded = $dvr->CmdWorkState();
} elsif ($cfgCmd eq "LogExport") {
$decoded = $dvr->LogExport($cfgFile);
} elsif ($cfgCmd eq "ConfigExport") {
$decoded = $dvr->ConfigExport($cfgFile);
} elsif ($cfgCmd eq "OEMInfo") {
$decoded = $dvr->CmdOEMInfo();
} elsif ($cfgCmd eq "OPStorageManagerClear") {
$decoded = $dvr->CmdOPStorageManager({
Action => "Clear",
PartNo => 0,
SerialNo => 0,
Type => "Data",
});
} elsif ($cfgCmd eq "OPStorageManagerPartition") {
$decoded = $dvr->CmdOPStorageManager({
Action => "Partition",
PartNo => 0,
PartitionSize => ( { "Record" => 853869 }, { "SnapShot" => 100000 } ),
SerialNo => 0 ,
});
} elsif ($cfgCmd eq "OPStorageManagerRecover") {
$decoded = $dvr->CmdOPStorageManager({
Action => "Recover",
PartNo => 0,
SerialNo => 0,
});
} elsif ($cfgCmd eq "OPStorageManagerRW") {
$decoded = $dvr->CmdOPStorageManager({
Action => "SetType",
PartNo => 0,
SerialNo => 0,
Type => "ReadWrite",
});
} elsif ($cfgCmd eq "OPStorageManagerRO") {
$decoded = $dvr->CmdOPStorageManager({
Action => "SetType",
PartNo => 0,
SerialNo => 0,
Type => "ReadOnly",
});
}
print Dumper $decoded;
#my $decoded = $dvr->CmdSystemFunction();
#my $decoded = $dvr->CmdOPFileQuery({
# BeginTime => "2016-07-01 22:00:00",
# EndTime => "2016-07-01 23:59:59",
# Channel => 1,
# # search all channels instead of single
# #HighChannel => 0,
# #LowChannel => 255,
# DriverTypeMask => "0x0000FFFF",
# Event => "*", # * - All; A - Alarm; M - Motion Detect; R - General; H - Manual;
# Type => "h264" #h264 or jpg
#});
#my $decoded = $dvr->CmdOPPlayBack({
# Action => "Claim",
# EndTime => '2015-05-04 17:59:24',
# StartTime => '2015-05-04 17:59:09',
# Parameter => {
# FileName => '/idea0/2015-05-04/002/17.59.09-17.59.24[M][@12480][0].h264',
# PlayMode => "ByName",
# TransMode => "TCP",
# Value => 0
# }
#});
my $decoded = $dvr->CmdAlarmInfo({
Channel => 0,
Event => "VideoMotion",
StartTime => "2016-07-03 03:36:11",
Status => "Start"
});
#my $decoded = $dvr->CmdOPNetAlarm(); #FIXME
#my $decoded = $dvr->CmdAlarmCenterMsg(); #FIXME
my $decoded = $dvr->CmdOPNetKeyboard({
Status => "KeyUp",
Value => "0",
});
#my $decoded = $dvr->CmdSnap(); #FIXME unsuppoted?
#my $decoded = $dvr->CmdOPLogQuery({
# BeginTime => "2014-01-01 00:00:00",
# EndTime => "2016-06-29 00:00:00",
# LogPosition => 0,
# Type => "LogAll",
#});
#my $pkt = {
# Name => '',
#};
#my $decoded = $dvr->PrepareGenericDownloadCommand(IPcam::PHOTO_GET_REQ, $pkt, "out.dat");
print Dumper $decoded;
$dvr->disconnect();
__END__
=head1 NAME
./dvr.pl - utility for working with nvr data
=head1 SYNOPSIS
./dvr.pl [options]
=head1 OPTIONS
=over 8
=item B<-help>
Print a brief help message and exits.
=item B<-of>
Path to output file filename.
=item B<-u>
username
=item B<-p>
password
=item B<-host>
nvr hostname or ip address
=item B<-port>
nvr CMS port
=item B<-c>
nvr command: OPTimeSetting, Users, Groups, WorkState, StorageInfo, OEMInfo, LogExport, ConfigExport, OPStorageManagerClear
=back
=head1 DESCRIPTION
B<This program> can control the NVR.
=cut
@667bdrm
Copy link
Author

667bdrm commented May 16, 2017

This tool must use the same port that CMS software uses to connect NVR. Usually it can be seen at the NVR network settings. Also please make sure that NVR uses compatible protocol. This tool designed for the HiSilicon NVRs running SOFIA ui. The different vendors could use modified incompatible protocol.

@roberthunt
Copy link

I managed to get this script talking to my NVR device (https://www.amazon.co.uk/gp/product/B017DCMB22)

I used the following command line:
perl ipcam-clock-set.pl -u admin -p tlJwpbo6 -host 192.168.1.137 -port 34567

The password in the above command is actually blank (an empty string) that has been hashed by some unknown algorithm. I captured it using Wireshark whilst logging in via the ActiveX based web client. I came across this paper which discusses some aspects of these NVR systems which I found quite interesting: http://www.i-1.nl/blog/wp-content/uploads/CCF-paper-Forensic-reliabilty-DVR.pdf

Here is the output of the command:

Setting clock for: host = 192.168.1.137 port = 34567
reply: head_flag=ff version=1 session=0x48 sequence=0 channel=0 end_flag=0 msgid=1001 size = 128 lastcommand =  msgid = 1000
SessionID = 0x00000072
AliveInterval = 21
reply: head_flag=ff version=1 session=0x72 sequence=0 channel=0 end_flag=0 msgid=1021 size = 594 lastcommand = SystemInfo msgid = 1020
$VAR1 = {
          'SessionID' => '0x00000048',
          'ChannelNum' => 4,
          'ExtraChannel' => 0,
          'Ret' => 100,
          'AliveInterval' => 21,
          'DeviceType ' => 'HVR'
        };
$VAR1 = {
          'UpdataType' => '0x00000000',
          'HardWare' => 'NBD6904T-F',
          'TalkInChannel' => 1,
          'VideoInChannel' => 0,
          'BuildTime' => '2015-12-16 10:26:08',
          'EncryptVersion' => 'Unknown',
          'HardWareVersion' => 'Unknown',
          'VideoOutChannel' => 1,
          'DeviceRunTime' => '0x0003F182',
          'AlarmInChannel' => 0,
          'TalkOutChannel' => 1,
          'ExtraChannel' => 0,
          'SerialNo' => '82be84624cc5e9bb',
          'AlarmOutChannel' => 0,
          'CombineSwitch' => 0,
          'UpdataTime' => '',
          'SoftWareVersion' => 'V4.02.R11.85100098.12001.130000.00000',
          'DigChannel' => 4,
          'AudioInChannel' => 0
        };
System running:179 day(s): 0 year(s), 5 month(s), 29 day(s), 11 hour(s), 14 minute(s)
$VAR1 = {
          'SessionID' => '0x72',
          'SystemInfo' => {
                            'UpdataType' => '0x00000000',
                            'HardWare' => 'NBD6904T-F',
                            'TalkInChannel' => 1,
                            'VideoInChannel' => 0,
                            'BuildTime' => '2015-12-16 10:26:08',
                            'EncryptVersion' => 'Unknown',
                            'HardWareVersion' => 'Unknown',
                            'VideoOutChannel' => 1,
                            'DeviceRunTime' => '0x0003F182',
                            'AlarmInChannel' => 0,
                            'TalkOutChannel' => 1,
                            'ExtraChannel' => 0,
                            'SerialNo' => '82be84624cc5e9bb',
                            'AlarmOutChannel' => 0,
                            'CombineSwitch' => 0,
                            'UpdataTime' => '',
                            'SoftWareVersion' => 'V4.02.R11.85100098.12001.130000.00000',
                            'DigChannel' => 4,
                            'AudioInChannel' => 0
                          },
          'Ret' => 100,
          'Name' => 'SystemInfo'
        };
reply: head_flag=ff version=1 session=0x72 sequence=0 channel=0 end_flag=0 msgid=0 size = 58 lastcommand = AlarmInfo msgid = 1504
reply: head_flag=ff version=1 session=0x72 sequence=0 channel=0 end_flag=0 msgid=1551 size = 71 lastcommand = OPNetKeyboard msgid = 1550
$VAR1 = {
          'Name' => 'OPNetKeyboard',
          'Ret' => 100,
          'SessionID' => '0x00000072'
        };

I'm impressed it managed to connect and talk to the device, I'm not sure what I want to do with it yet but it gives me a starting point.

Thanks!

@667bdrm
Copy link
Author

667bdrm commented Feb 12, 2018

Latest version at https://github.com/667bdrm/sofiactl supporting hash calculation by default, no needs to grab it by sniffer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment