Skip to content

Instantly share code, notes, and snippets.

@jph
Created November 5, 2014 23:10
Show Gist options
  • Save jph/6a25bcee0344c33cc50b to your computer and use it in GitHub Desktop.
Save jph/6a25bcee0344c33cc50b to your computer and use it in GitHub Desktop.
/*****************************************
* Read QSee/Zmodo cameras *
* Forward stream to FIFO pipe *
* Author: Daniel Osborne *
* Based on IP Cam Viewer by Robert Chou *
* License: Public Domain *
*****************************************/
/* Version history
* 0.41 - 2013-05-03
* Fixed -h option not displaying help.
* 0.4 - 2013-04-21
* Add support for some Swann models
* Added Visionari patch by jasonblack
* Fix incorrect use of select timeout struct.
* 0.3 - 2012-01-26
* Add support for DVR-8104UV/8114HV and CnM Classic 4 Cam DVR
* 0.2 - 2011-11-12
* Got media port support working (for some models at least).
* Changed fork behavior to fix a bug, now parent only spawns children, it doesn't stream.
* 0.1 - 2011-08-26
* Initial version, working mobile port support, but buggy
*/
// Compile: gcc -Wall zmodopipe.c -o zmodopipe
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/un.h>
#include <stdio.h>
#include <string.h>
#include <netdb.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <stdarg.h>
//typedef enum bool {false=0, true=1,} bool;
typedef enum CameraModel
{
mobile = 1, // Q-See/Swann/Zmodo DVR w/mobile port
media, // Q-See/Zmodo w/media port
media_header, // Q-See/Zmodo w/media port and header packet
qt504, // Q-See QT-504 compatible model
dvr8104_mobile, // Zmodo DVR-8104/8114
cnmclassic, // CnM Classic 4 Cam
visionari, // Visionari 4/8 channel DVR
swannmedia, // Swann media
} CameraModel;
// Structure for logging into QSee/Zmodo DVR's mobile port
// Must be in network byte order
struct QSeeLoginMobile
{
int val1; // 4 Set to 64
int val2; // 4 Set to 0;
short val3; // 2 Set to 41
short val4; // 2 Set to 56
char user[32]; // 32 username field (len might be 24, not sure)
char pass[20]; // 20 password field
short ch; // 2 Camera channel (0 index)
short val5; // 2 unknown (reserved?)
}; // total size: 68 bytes
// Structure for logging into QSee/Zmodo DVR's media port
// Must be in network byte order
struct QSeeLoginMedia
{
char valc[47]; // 47 bytes, special values
char user[8]; // 8 username field
char vals[26]; // 26 unknown values
char pass[6]; // 6 password field
char filler[420]; //420 Filler (more unknown)
}; // Total size: 507 bytes
// Structure for logging into QSee QT-504
// There are two other structures, but simple byte arrays are used for those
struct QSee504Login
{
char vala[32]; // 44 bytes, special values
char user[8]; // 8 username field
char valb[28]; // 28 unknown values
char pass[6]; // 6 password field
char valc[30]; // 30 more unknown
char host[8]; // 8 Hostname, apparently (how big??)
char filler[32]; // 32 Filler (more unknown)
}; // Total size: 144 bytes
// Structure for logging into Zmodo DVR-8104
struct DVR8104MobileLogin
{
char vala[60]; // 60 bytes
char user[4]; // 4 username field(really 4?)
char valb[28]; // 26 unknown values
char pass[6]; // 6 password field
char filler[18]; // 18 Filler (more unknown)
}; // Total size: 116 bytes
// Structure for logging into CnM 4 Cam Classic CCTV
struct CnMClassicLogin
{
char vala[40]; // 40 bytes
char user[8]; // 8 username field
char valb[24]; // 24 unknown values
char pass[6]; // 6 password field
char filler[422]; //422 Filler (more unknown)
}; // Total size: 500 bytes
struct VisionariLogin
{
char vala[60]; // 60 bytes
char user[8]; // 4 username field
char valb[24]; // 24 unknown values
char pass[6]; // 6 password field
char filler[18]; // 18 Filler (more unknown)
}; // Total size: 112 bytes
// Structure for logging into QSee/Zmodo DVR's media port
// Must be in network byte order
struct SwannLoginMedia
{
char valc[47]; // 47 bytes, special values
char user[8]; // 8 username field
char vals[24]; // 24 unknown values
char pass[6]; // 6 password field
char filler[422]; //422 Filler (more unknown)
}; // Total size: 507 bytes
#define MAX_CHANNELS 16 // maximum channels to support (I've only seen max of 16).
struct globalArgs_t {
bool verbose; // -v duh
char *pipeName; // -n name to use for filename (ch # will be appended)
bool channel[MAX_CHANNELS]; // -c (support up to 16)
char *hostname; // -s hostname to connect to
unsigned short port; // -p port number
CameraModel model; // -m model to use
char *username; // -u login username
char *password; // -a login password
int timer; // -t alarm timer
} globalArgs = {0};
extern char *optarg;
const char *optString = "vn:c:p:s:m:u:a:t:h?";
int g_childPids[MAX_CHANNELS] = {0};
int g_cleanUp = false;
char g_errBuf[256]; // This will contain the error message for perror calls
int g_processCh = -1; // Channel this process will be in charge of (-1 means parent)
void sigHandler(int sig);
void display_usage(char *name);
int printMessage(bool verbose, const char *message, ...);
int ConnectViaMobile(int sockFd, int channel);
int ConnectViaMedia(int sockFd, int channel);
int ConnectQT504(int sockFd, int channel);
int ConnectDVR8104ViaMobile(int sockFd, int channel);
int ConnectCnMClassic(int sockFd, int channel);
int ConnectSwannViaMedia(int sockFd, int channel);
int ConnectVisionari(int sockFd, int channel);
// Function pointer list
int (*pConnectFunc[])(int, int) = {
NULL,
ConnectViaMobile,
ConnectViaMedia,
ConnectViaMedia,
ConnectQT504,
ConnectDVR8104ViaMobile,
ConnectCnMClassic,
ConnectVisionari,
ConnectSwannViaMedia,
};
void printBuffer(char *pbuf, size_t len)
{
int n;
printMessage(false, "Length: %lu\n", (unsigned long)len);
for(n=0; n < len ; n++)
{
printf( "%02x",(unsigned char)(pbuf[n]));
if( ((n + 1) % 8) == 0 )
printf(" "); // Make hex string slightly more readable
}
printf("\n");
}
int main(int argc, char**argv)
{
char pipename[256];
struct addrinfo hints, *server;
struct sockaddr_in serverAddr;
int retval = 0;
char recvBuf[2048];
struct sigaction sapipe, oldsapipe, saterm, oldsaterm, saint, oldsaint, sahup, oldsahup;
char opt;
int loopIdx;
int outPipe = -1;
#ifdef DOMAIN_SOCKETS
struct sockaddr_un addr;
#endif
int sockFd = -1;
struct timeval tv;
#ifdef NON_BLOCK_READ
struct timeval tv, tv_sel;
#endif
struct linger lngr;
int status = 0;
int pid = 0;
lngr.l_onoff = false;
lngr.l_linger = 0;
// Process arguments
// Clear and set defaults
memset(&globalArgs, 0, sizeof(globalArgs));
globalArgs.hostname =
globalArgs.pipeName = "zmodo";
globalArgs.model = media;
globalArgs.username =
globalArgs.password = "admin";
// Read command-line
while( (opt = getopt(argc, argv, optString)) != -1 )
{
switch( opt )
{
case 'v':
globalArgs.verbose = true;
break;
case 'c':
globalArgs.channel[atoi(optarg) - 1] = true;
break;
case 'n':
globalArgs.pipeName = optarg;
break;
case 's':
globalArgs.hostname = optarg;
break;
case 'p':
globalArgs.port = atoi(optarg);
break;
case 'm':
globalArgs.model = atoi(optarg);
break;
case 'u':
globalArgs.username = optarg;
break;
case 'a':
globalArgs.password = optarg;
break;
case 't':
globalArgs.timer = atoi(optarg);
break;
case 'h':
// Fall through
case '?':
// Fall through
default:
display_usage(argv[0]);
return 0;
}
}
// Set up default values based on provided values (if any)
if( !globalArgs.port )
{
switch( globalArgs.model )
{
case mobile:
globalArgs.port = 18600;
break;
case media:
case media_header:
case cnmclassic:
case swannmedia:
globalArgs.port = 9000;
break;
case qt504:
globalArgs.port = 6036;
break;
case dvr8104_mobile:
globalArgs.port = 8888;
break;
case visionari:
globalArgs.port = 1115;
break;
}
}
memset(&saint, 0, sizeof(saint));
memset(&saterm, 0, sizeof(saterm));
memset(&sahup, 0, sizeof(sahup));
// Ignore SIGPIPE
sapipe.sa_handler = sigHandler;
sigaction(SIGPIPE, &sapipe, &oldsapipe);
// Handle SIGTERM & SIGING
saterm.sa_handler = sigHandler;
sigaction(SIGTERM, &saterm, &oldsaterm);
saint.sa_handler = sigHandler;
sigaction(SIGINT, &saint, &oldsaint);
signal( SIGUSR1, SIG_IGN ); // Ignore SIGUSR1 in parent process
// SIGUSR2 is used to reset the pipe and connection
sahup.sa_handler = sigHandler;
sigaction(SIGUSR2, &sahup, &oldsahup);
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_protocol = IPPROTO_TCP;
retval = getaddrinfo(globalArgs.hostname, NULL, &hints, &server);
if( retval != 0 )
{
printMessage(false, "getaddrinfo failed: %s\n", gai_strerror(retval));
return 1;
}
serverAddr.sin_addr = ((struct sockaddr_in*)server->ai_addr)->sin_addr;
serverAddr.sin_port = htons(globalArgs.port);
do
{
if( pid )
{
printMessage(true, "Child %i returned: %i\n", pid, status);
if( g_cleanUp == 2 )
g_cleanUp = false; // Ignore SIGHUP
}
// Create a fork for each camera channel to stream
for( loopIdx=0;loopIdx<MAX_CHANNELS;loopIdx++ )
{
//static bool hitFirst = false;
if( globalArgs.channel[loopIdx] == true )
{
// Always fork if we're starting up, or if the pid of the dead child process matches
if( pid == 0 || g_childPids[loopIdx] == pid )
g_childPids[loopIdx] = fork();
// Child Process
if( g_childPids[loopIdx] == 0 )
{
// SIGUSR1 is used to reset the pipe and connection
sahup.sa_handler = sigHandler;
sigaction(SIGUSR1, &sahup, &oldsahup);
memset(g_childPids, 0, sizeof(g_childPids));
g_processCh = loopIdx;
break;
}
// Error
else if( g_childPids[loopIdx] == -1 )
{
printMessage(false, "fork failed\n");
return 1;
}
}
}
}
while( (pid = wait(&status)) > 0 && g_cleanUp != true );
if( g_processCh != -1 )
{
// At this point, g_processCh contains the camera number to use
sprintf(pipename, "/tmp/%s%i", globalArgs.pipeName, g_processCh);
tv.tv_sec = 5; // Wait 5 seconds for socket data
tv.tv_usec = 0;
#ifndef DOMAIN_SOCKETS
retval = mkfifo(pipename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
if( retval != 0 )
{
sprintf(g_errBuf, "Ch %i: Failed to create pipe", g_processCh+1);
perror(g_errBuf);
}
#endif
while( !g_cleanUp )
{
int flag = true;
#ifdef NON_BLOCK_READ
fd_set readfds;
#endif
// Initialize the socket and connect
sockFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if( setsockopt(sockFd, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(tv)))
{
sprintf(g_errBuf, "Ch %i: %s", g_processCh+1, "Failed to set socket timeout");
perror(g_errBuf);
}
if( setsockopt(sockFd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(flag)))
{
sprintf(g_errBuf, "Ch %i: %s", g_processCh+1, "Failed to set TCP_NODELAY");
perror(g_errBuf);
}
if( setsockopt(sockFd, SOL_SOCKET, SO_LINGER, (char*)&lngr, sizeof(lngr)))
{
sprintf(g_errBuf, "Ch %i: %s", g_processCh+1, "Failed to set SO_LINGER");
perror(g_errBuf);
}
retval = connect(sockFd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
if( globalArgs.verbose )
printMessage(true, "Ch %i: Connect result: %i\n", g_processCh+1, retval);
if( retval == -1 && errno != EINPROGRESS )
{
int sleeptime = 10;
if( globalArgs.verbose )
{
sprintf(g_errBuf, "Ch %i: %s", g_processCh+1, "Failed to connect");
perror(g_errBuf);
printMessage(true, "Waiting %i seconds.\n", sleeptime);
}
close(sockFd);
sockFd = -1;
sleep(sleeptime);
continue;
}
if( retval == -1 && errno != EINPROGRESS )
{
sprintf(g_errBuf, "Ch %i: %s", g_processCh+1, "Failed to connect");
perror(g_errBuf);
return 1;
}
retval = 0;
if( pConnectFunc[globalArgs.model](sockFd, g_processCh) != 0 )
{
printMessage(true, "Login failed, bailing.\nDid you select the right model?\n");
close(sockFd);
sockFd = -1;
return 1;
}
#ifdef NON_BLOCK_READ
if( fcntl(sockFd, F_SETFL, O_NONBLOCK) == -1 ) // non-blocking sockets
{
sprintf(g_errBuf, "Ch %i: %s", g_processCh+1, "Failed to set O_NONBLOCK");
perror(g_errBuf);
}
FD_ZERO(&readfds);
FD_SET(sockFd, &readfds );
#endif
// the stream sometimes goes grey,
// this alarm should periodically reset the stream
if(globalArgs.timer)
alarm(globalArgs.timer);
// Now we are connected and awaiting stream
do
{
int read;
#ifdef NON_BLOCK_READ
tv_sel.tvsec = 0;
tv_sel.tv_usec = 10000; // Wait 10 ms
if( select( sockFd+1, &readfds, NULL, NULL, &tv_sel) == -1)
{
sprintf(g_errBuf, "Ch %i: %s", g_processCh+1, "Select failed");
perror(g_errBuf);
close(sockFd);
sockFd = -1;
break; // Connection may have died, reset
}
if (!FD_ISSET(sockFd, &readfds))
{
if( globalArgs.verbose )
printMessage(true, "\nCh %i: %s", g_processCh+1, "Read would block\n");
continue; // Not ready yet
}
#endif
// Read actual h264 data from camera
read = recv(sockFd, recvBuf, sizeof(recvBuf), 0);
// Server disconnected, close the socket so we can try to reconnect
if( read <= 0 )
{
#ifdef NON_BLOCK_READ
if( errno == EAGAIN || errno == EWOULDBLOCK )
{
if( globalArgs.verbose )
printMessage(true, "\nCh %i: %s", g_processCh+1, "Read would block\n");
continue;
}
#endif
if( globalArgs.verbose )
printMessage(true, "Ch %i: Socket closed. Receive result: %i\n", g_processCh+1, read);
close(sockFd);
sockFd = -1;
break;
}
if( globalArgs.verbose )
{
printf(".");
fflush(stdout);
}
#ifdef DOMAIN_SOCKETS
if( outPipe == -1 )
{
outPipe = socket(AF_UNIX, SOCK_STREAM, 0);
if( outPipe == -1 )
perror("Error creating socket");
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, pipename, sizeof(addr.sun_path) - 1);
if( bind(outPipe, (struct sockaddr*)&addr, sizeof(addr)) == -1 )
perror("Error binding socket");
}
#else
// Open the pipe if it wasn't previously opened
if( outPipe == -1 )
outPipe = open(pipename, O_WRONLY | O_NONBLOCK);
#endif
// send to pipe
if( outPipe != -1 )
{
if( (retval = write(outPipe, recvBuf, read)) == -1)
{
if( errno == EAGAIN || errno == EWOULDBLOCK )
{
if( globalArgs.verbose )
printMessage(true, "\nCh %i: %s", g_processCh+1, "Reader isn't reading fast enough, discarding data. Not enough processing power?\n");
// Right now we discard data, should be a way to buffer maybe?
continue;
}
// reader closed the pipe, wait for it to be opened again.
else if( globalArgs.verbose )
{
sprintf(g_errBuf, "Ch %i: %s", g_processCh+1, "Pipe closed");
perror(g_errBuf);
}
#ifndef DOMAIN_SOCKETS
close(outPipe);
outPipe = -1;
#endif
close(sockFd);
sockFd = -1;
continue;
}
else
{
if( globalArgs.verbose )
{
printf("\b \b");
fflush(stdout);
}
}
}
}
while( sockFd != -1 && !g_cleanUp );
// If we receive a SIGUSR1, close and reset everything
// Then start the loop over again.
if( g_cleanUp >= 2 )
{
g_cleanUp = false;
if( sockFd != -1 )
close(sockFd);
sockFd = -1;
if( g_cleanUp != 3 )
{
if( outPipe != -1 )
close(outPipe);
outPipe = -1;
}
}
}
if( globalArgs.verbose )
printMessage(true, "Exiting loop: %i\n", g_cleanUp);
// Received signal to exit, cleanup
close(outPipe);
close(sockFd);
unlink(pipename);
}
// Restore old signal handler
sigaction(SIGPIPE, &oldsapipe, NULL);
sigaction(SIGTERM, &oldsaterm, NULL);
sigaction(SIGINT, &oldsaint, NULL);
sigaction(SIGUSR1, &oldsahup, NULL);
freeaddrinfo(server);
// Kill all children (if any)
for( loopIdx=0;loopIdx<MAX_CHANNELS;loopIdx++ )
{
if( globalArgs.channel[loopIdx] > 0 )
kill( globalArgs.channel[loopIdx], SIGTERM );
}
return 0;
}
void display_usage(char *name)
{
printf("Usage: %s [options]\n\n", name);
printf("Where [options] is one of:\n\n"
" -s <string>\tIP to connect to\n"
" -t <int>\tSend a timer interrupt every x seconds.\n"
" -p <int>\tPort number to connect to\n"
" -c <int>\tChannels to stream (can be specified multiple times)\n"
" -n <string>\tBase filename of pipe (ch# will be appended)\n"
" -v\t\tVerbose output\n"
" -u <string>\tUsername\n"
" -a <string>\tPassword\n"
" -m <int>\tMode to use (ie. mobile/media)\n"
" \t\t1 - Use mobile port (safest, default)\n"
" \t\t2 - Use media port (Works for some models, ie. Zmodo 9104)\n"
" \t\t3 - Use media port w/header (Other models, please test)\n"
" \t\t4 - Use QT5 family (ie. QT504, QT528)\n"
" \t\t5 - Zmodo DVR-8104UV compatible (also DVR-8114HV)\n"
" \t\t6 - CnM Classic 4 Cam DVR\n"
" \t\t7 - Visionari 4/8 Channel DVR\n"
" \t\t8 - Swann DM-70D and compatible\n"
"\n");
}
void sigHandler(int sig)
{
printMessage(true, "Received signal: %i\n", sig);
switch( sig )
{
case SIGTERM:
case SIGINT:
// Kill the main loop
g_cleanUp = true;
break;
case SIGUSR1:
case SIGALRM:
case SIGPIPE:
g_cleanUp = 2;
break;
case SIGUSR2:
g_cleanUp = 3;
break;
}
}
int printMessage(bool verbose, const char *message, ...)
{
char msgBuf[2048];
int ret;
va_list argptr;
va_start(argptr, message);
if( g_processCh == -1 )
sprintf(msgBuf, "Main: %s", message);
else
sprintf(msgBuf, "Ch %i: %s", g_processCh, message);
if( !( verbose && !globalArgs.verbose) )
ret = vprintf(msgBuf, argptr);
va_end(argptr);
return ret;
}
// This is more compatible, but less reliable than the Media mode.
// h264 decoder shows "This stream was generated by a broken encoder, invalid 8x8 inference"
// Output is 320x240@25fps ~160kbit/s VBR
int ConnectViaMobile(int sockFd, int channel)
{
struct QSeeLoginMobile loginBuf = {0};
int retval;
int header;
char recvBuf[128];
// do writing
// Setup login buffer
loginBuf.val1 = htonl(64);
loginBuf.val3 = htons(10496);
loginBuf.val4 = htons(14336);
loginBuf.ch = htons(channel);
strcpy(loginBuf.user, globalArgs.username);
strcpy(loginBuf.pass, globalArgs.password);
retval = send(sockFd, (char*)(&loginBuf), sizeof(loginBuf), 0);
if( globalArgs.verbose )
{
printMessage(true, "Ch %i: Send result: %i\n", channel+1, retval);
}
// do reading
// Get header length (4 bytes)
retval = recv(sockFd, &header, sizeof(header), 0);
if( retval != 4 )
{
printMessage(true, "Ch %i: Receive 1 failed.\n", channel+1);
return 1;
}
header = ntohl(header);
// Get next section (20 bytes)
retval = recv(sockFd, recvBuf, header, 0);
if( retval != header )
{
printMessage(true, "Ch %i: Receive 2 failed.\n", channel+1);
return 1;
}
// Check login status
if( recvBuf[16] != 1 )
{
printMessage(true, "Ch %i: Login failed: ", channel+1);
int x;
for( x=0;x<header;x++)
printMessage(true, "%02x", recvBuf[x]);
return 1;
}
// Read next sections
retval = recv(sockFd, recvBuf, sizeof(header), 0);
if( retval != sizeof(header) && recvBuf[3] != 0 )
{
printMessage(true, "Ch %i: Problem length (4): %i, recvBuf[3]: %i\n", channel+1, retval, (int)recvBuf[3]);
return 1;
}
header = recvBuf[3] & 0xFF;
retval = recv(sockFd, recvBuf, header, 0);
if( retval != header )
{
printMessage(true, "Ch %i: Receive 3 failed.\n", channel+1);
return 1;
}
// Read another 27 bytes
header = 27;
retval = recv(sockFd, recvBuf, header, 0);
if( retval != header )
{
printMessage(true, "Ch %i: Receive 4 failed.\n", channel+1);
return 1;
}
// If we got here, the stream will be waiting for us to recv.
return 0;
}
// This is less compatible, but more reliable than the mobile mode
// Output is 704x480@25fps 1200kbit/s VBR
int ConnectViaMedia(int sockFd, int channel)
{
struct QSeeLoginMedia loginBuf;
int retval;
static bool beenHere = false;
memset(&loginBuf, 0, sizeof(loginBuf));
// Some models take a special header first
// Mine doesn't, so this is untested
if( globalArgs.model == media_header )
{
retval = send(sockFd, "0123456", 7, 0);
if( globalArgs.verbose )
printMessage(true, "Ch %i: Send result: %i\n", channel+1, retval);
}
// Setup login buffer
loginBuf.valc[10] = 0x01;
*(short*)&loginBuf.valc[14] = htons(0x035f + (1 << channel));
loginBuf.valc[30] = 0x01;
loginBuf.valc[26] = 0x68;
loginBuf.valc[34] = 0x10;
*(short*)&loginBuf.valc[37] = htons(1 << channel);
loginBuf.valc[42] = 1;
loginBuf.valc[46] = 1;
strcpy(loginBuf.user, globalArgs.username);
strcpy(loginBuf.pass, globalArgs.password);
if( globalArgs.verbose && beenHere == false )
{
printBuffer((char*)&loginBuf, sizeof(loginBuf));
beenHere = true;
}
retval = send(sockFd, (char*)(&loginBuf), sizeof(loginBuf), 0);
if( globalArgs.verbose )
printMessage(true, "Ch %i: Send result: %i\n", channel+1, retval);
/* // Not sure when this is used, possibly some other model
// do reading
// Get header length (8 bytes)
retval = recv(sockFd, &recvBuf, 8, 0);
if( retval != 8 )
{
if( retval != 16 )
{
printMessage(true, "Receive 1 failed.\n");
return 1;
}
}
*/
// If we got here, the stream will be waiting for us to recv.
return 0;
}
// QT5 Family (ie. QT-504)
// the QT-504 is a bit different, it sends 3 packets for login.
int ConnectQT504(int sockFd, int channel)
{
char suppLoginBuf[88] = {0};
struct QSee504Login loginBuf;
int retval;
char recvBuf[532];
static bool beenHere = false;
memset(&loginBuf, 0, sizeof(loginBuf));
loginBuf.vala[0] = 0x31;
loginBuf.vala[1] = 0x31;
loginBuf.vala[2] = 0x31;
loginBuf.vala[3] = 0x31;
loginBuf.vala[4] = 0x88;
loginBuf.vala[8] = 0x01;
loginBuf.vala[9] = 0x01;
loginBuf.vala[12] = 0xff;
loginBuf.vala[13] = 0xff;
loginBuf.vala[14] = 0xff;
loginBuf.vala[15] = 0xff;
loginBuf.vala[16] = 0x04;
loginBuf.vala[20] = 0x78;
loginBuf.vala[24] = 0x03;
strcpy(loginBuf.user, globalArgs.username);
strcpy(loginBuf.pass, globalArgs.password);
gethostname(loginBuf.host, sizeof(loginBuf.host));
loginBuf.filler[22] = 0x50;
loginBuf.filler[23] = 0x56;
loginBuf.filler[24] = 0xc0;
loginBuf.filler[25] = 0x08;
loginBuf.filler[28] = 0x04;
if( globalArgs.verbose && beenHere == false )
{
printBuffer((char*)&loginBuf, sizeof(loginBuf));
}
// Send the login packet (1 of 4)
retval = send(sockFd, (char*)(&loginBuf), sizeof(loginBuf), 0);
if( globalArgs.verbose )
{
printMessage(true, "Ch %i: Send 1 result: %i\n", channel+1, retval);
}
// do reading
// Get header length (4 bytes)
retval = recv(sockFd, &recvBuf, 532, 0);
// Verify send was successful
if( retval != 532 )
{
printMessage(true, "Ch %i: Receive 1 failed: %i\n", channel+1, retval);
// return 1;
}
/* Inbetween the 2 and last packets there is another packet.
* It seems to be optional.
*/
memset(suppLoginBuf, 0, 88);
suppLoginBuf[0] = 0x31;
suppLoginBuf[1] = 0x31;
suppLoginBuf[2] = 0x31;
suppLoginBuf[3] = 0x31;
suppLoginBuf[4] = 0x50;
suppLoginBuf[8] = 0x03;
suppLoginBuf[9] = 0x04;
suppLoginBuf[12] = 0xf0;
suppLoginBuf[13] = 0xb7;
suppLoginBuf[14] = 0x3d;
suppLoginBuf[15] = 0x08;
suppLoginBuf[16] = 0x03;
suppLoginBuf[20] = 0x40;
suppLoginBuf[25] = 0xf8;
suppLoginBuf[32] = 0x01;
suppLoginBuf[33] = 0xf8;
suppLoginBuf[40] = 0x02;
suppLoginBuf[41] = 0xf8;
suppLoginBuf[48] = 0x03;
suppLoginBuf[49] = 0xf8;
suppLoginBuf[56] = 0x40;
suppLoginBuf[57] = 0xf8;
suppLoginBuf[60] = 0x97;
suppLoginBuf[61] = 0xf0;
suppLoginBuf[64] = 0x41;
suppLoginBuf[65] = 0xf8;
// Send the next packet (2 of 4)
retval = send(sockFd, suppLoginBuf, 88, 0);
if( globalArgs.verbose )
{
printMessage(true, "Ch %i: Send 2 result: %i\n", channel+1, retval);
}
retval = 0;
{
int ret = 0;
while( (ret = recv(sockFd, recvBuf, sizeof(recvBuf), 0)) > 0 )
retval += ret;
}
suppLoginBuf[0] = 0x31;
suppLoginBuf[1] = 0x31;
suppLoginBuf[2] = 0x31;
suppLoginBuf[3] = 0x31;
suppLoginBuf[4] = 0x34;
suppLoginBuf[8] = 0x01;
suppLoginBuf[9] = 0x02;
suppLoginBuf[20] = 0x24;
*(short*)&suppLoginBuf[36] = htons(1 << channel);
*(short*)&suppLoginBuf[52] = htons(1 << channel);
if( globalArgs.verbose && beenHere == false )
{
printBuffer((char*)&suppLoginBuf, 60);
beenHere = true;
}
// Send the next packet (3 of 4)
retval = send(sockFd, suppLoginBuf, 60, 0);
if( globalArgs.verbose )
{
printMessage(true, "Ch %i: Send 3 result: %i\n", channel+1, retval);
}
// do reading
retval = recv(sockFd, recvBuf, 124, 0);
// Verify send was successful
if( retval <= 0 )
{
printMessage(true, "Ch %i: Receive 3 failed.\n", channel+1);
return 1;
}
else
printMessage(true, "Ch %i: Receive 3 result: %i bytes.\n", channel+1, retval);
// Reuse the old buffer, last three bytes should still be 0
//suppLoginBuf[5] = 0;
// Send the last packet (4 of 4)
//retval = send(sockFd, suppLoginBuf, 8, 0);
//if( globalArgs.verbose )
//{
// printMessage(true, "Ch %i: Send 4 result: %i\n", channel+1, retval);
//}
// If we got here, the stream will be waiting for us to recv.
return 0;
}
// Output is 352x240@25fps VBR
int ConnectDVR8104ViaMobile(int sockFd, int channel)
{
struct DVR8104MobileLogin loginBuf;
int retval;
static bool beenHere = false;
memset(&loginBuf, 0, sizeof(loginBuf));
// Setup login buffer
loginBuf.vala[3] = 0x70;
loginBuf.vala[4] = 0x01;
loginBuf.vala[8] = 0x28;
loginBuf.vala[10] = 0x04;
loginBuf.vala[12] = 0x03;
loginBuf.vala[14] = 0x07;
loginBuf.vala[16] = 0x48;
loginBuf.vala[18] = 0x24;
loginBuf.vala[20] = 0x20;
loginBuf.vala[21] = 0x20;
loginBuf.vala[22] = 0x20;
loginBuf.vala[23] = 0x21;
loginBuf.vala[24] = 0x20;
loginBuf.vala[25] = 0x20;
loginBuf.vala[26] = 0x20;
loginBuf.vala[36] = 0x4d;
loginBuf.vala[37] = 0x4f;
loginBuf.vala[38] = 0x42;
loginBuf.vala[39] = 0x49;
loginBuf.vala[40] = 0x4c;
loginBuf.vala[41] = 0x45;
loginBuf.vala[56] = 0x29;
loginBuf.vala[58] = 0x38;
loginBuf.valb[0] = 0x6e;
loginBuf.valb[27] = 0x6e;
loginBuf.filler[10] = 0x01;
loginBuf.filler[15] = channel;
strcpy(loginBuf.user, globalArgs.username);
strcpy(loginBuf.pass, globalArgs.password);
if( globalArgs.verbose && beenHere == false )
{
printBuffer((char*)&loginBuf, sizeof(loginBuf));
beenHere = true;
}
retval = send(sockFd, (char*)(&loginBuf), sizeof(loginBuf), 0);
if( retval != sizeof(loginBuf) )
{
printMessage(true, "Ch %i: Send failed, was: %i, should be: %i\n", channel+1, retval, (int)sizeof(loginBuf));
return 1;
}
if( globalArgs.verbose )
printMessage(true, "Ch %i: Send result: %i\n", channel+1, retval);
// If we got here, the stream will be waiting for us to recv.
return 0;
}
// CnM Classic 4 Cam
// http://194.150.201.35/cnmsecure/support/4CamClassicKit.htm
int ConnectCnMClassic(int sockFd, int channel)
{
struct CnMClassicLogin loginBuf;
int retval;
char recvBuf[532];
static bool beenHere = false;
memset(&loginBuf, 0, sizeof(loginBuf));
loginBuf.vala[3] = 0x01;
loginBuf.vala[7] = 0x03;
loginBuf.vala[8] = 0x0b;
loginBuf.vala[19] = 0x68;
loginBuf.vala[23] = 0x01;
loginBuf.vala[27] = 0x54;
*(short*)&loginBuf.vala[30] = htons(1 << channel);
strcpy(loginBuf.user, globalArgs.username);
strcpy(loginBuf.pass, globalArgs.password);
if( globalArgs.verbose && beenHere == false )
{
printBuffer((char*)&loginBuf, sizeof(loginBuf));
}
// Send the login packet
retval = send(sockFd, (char*)(&loginBuf), sizeof(loginBuf), 0);
if( globalArgs.verbose )
{
printMessage(true, "Ch %i: Send 1 result: %i\n", channel+1, retval);
}
// do reading (1 of 2)
retval = recv(sockFd, &recvBuf, 8, 0);
// Verify send was successful
if( retval != 8 && recvBuf[0] != 1 )
{
printMessage(true, "Ch %i: Receive 1 failed: %i\n", channel+1, retval);
printBuffer((char*)&recvBuf, sizeof(recvBuf));
return 1;
}
// do reading (1 of 2)
retval = recv(sockFd, &recvBuf, 520, 0);
// Verify send was successful
if( retval != 520 )
{
printMessage(true, "Ch %i: Receive 2 failed: %i\n", channel+1, retval);
printBuffer((char*)&recvBuf, sizeof(recvBuf));
return 1;
}
// If we got here, the stream will be waiting for us to recv.
return 0;
}
// Visionari 4/8 Channel DVR
int ConnectVisionari(int sockFd, int channel)
{
struct VisionariLogin loginBuf;
int retval;
static bool beenHere = false;
memset(&loginBuf, 0, sizeof(loginBuf));
// Setup login buffer
loginBuf.vala[3] = 0x70;
loginBuf.vala[4] = 0x01;
loginBuf.vala[8] = 0x28;
loginBuf.vala[10] = 0x04;
loginBuf.vala[12] = 0x03;
loginBuf.vala[14] = 0x07;
loginBuf.vala[16] = 0x48;
loginBuf.vala[18] = 0x24;
loginBuf.vala[20] = 0x30;
loginBuf.vala[21] = 0x30;
loginBuf.vala[22] = 0x30;
loginBuf.vala[23] = 0x31;
loginBuf.vala[24] = 0x30;
loginBuf.vala[25] = 0x30;
loginBuf.vala[26] = 0x30;
loginBuf.vala[36] = 0x4d;
loginBuf.vala[37] = 0x4f;
loginBuf.vala[38] = 0x42;
loginBuf.vala[39] = 0x49;
loginBuf.vala[40] = 0x4c;
loginBuf.vala[41] = 0x45;
loginBuf.vala[56] = 0x29;
loginBuf.vala[58] = 0x38;
loginBuf.filler[10] = 0x01;
loginBuf.filler[15] = channel;
strcpy(loginBuf.user, globalArgs.username);
strcpy(loginBuf.pass, globalArgs.password);
if( globalArgs.verbose && beenHere == false )
{
printBuffer((char*)&loginBuf, sizeof(loginBuf));
beenHere = true;
}
retval = send(sockFd, (char*)(&loginBuf), sizeof(loginBuf), 0);
if( retval != sizeof(loginBuf) )
{
printf("Ch %i: Send failed, was: %i, should be: %i\n", channel+1, retval, (int)sizeof(loginBuf));
return 1;
}
if( globalArgs.verbose )
printf("Ch %i: Send result: %i\n", channel+1, retval);
// If we got here, the stream will be waiting for us to recv.
return 0;
}
// For some Swann models (Hardware version DM-70D, Device type DVR04B)
int ConnectSwannViaMedia(int sockFd, int channel)
{
struct SwannLoginMedia loginBuf;
int retval;
static bool beenHere = false;
char recvBuf[16];
short *shrtval;
memset(&loginBuf, 0, sizeof(loginBuf));
// Setup login buffer
loginBuf.valc[10] = 0x01;
shrtval = (short*)&loginBuf.valc[14];
if( channel == 1 )
*shrtval = htons(0x0324);
else
*shrtval = htons(0x0324 + channel);
loginBuf.valc[26] = 0x68;
loginBuf.valc[30] = 0x01;
loginBuf.valc[34] = 0x10;
*(short*)&loginBuf.valc[37] = htons(1 << channel);
loginBuf.valc[42] = 1;
loginBuf.valc[46] = 1;
strcpy(loginBuf.user, globalArgs.username);
strcpy(loginBuf.pass, globalArgs.password);
if( globalArgs.verbose && beenHere == false )
{
printBuffer((char*)&loginBuf, sizeof(loginBuf));
beenHere = true;
}
retval = send(sockFd, (char*)(&loginBuf), sizeof(loginBuf), 0);
printMessage(true, "Ch %i: Send result: %i\n", channel+1, retval);
// before video stream, a small packet is sent
// Get header length (8 bytes)
retval = recv(sockFd, &recvBuf, 8, 0);
if( retval != 8 )
{
if( retval != 16 )
{
printMessage(false, "Receive 1 failed.\n");
return 1;
}
}
// If we got here, the stream will be waiting for us to recv.
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment