Created
November 5, 2014 23:10
-
-
Save jph/6a25bcee0344c33cc50b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/***************************************** | |
* 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