Skip to content

Instantly share code, notes, and snippets.

@TRPB
Last active October 18, 2019 20:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TRPB/370ada4d1749725d5bd9492201d0df9a to your computer and use it in GitHub Desktop.
Save TRPB/370ada4d1749725d5bd9492201d0df9a to your computer and use it in GitHub Desktop.
"use strict";
//This is a line for line port of the python exampe at https://github.com/tvheadend/tvheadend/tree/master/lib/py/tvh
//I have no idea how this works, sorry!
var net = require('net');
var crypto = require('crypto');
var HOST = 'media';
var PORT = 9982;
const HMF_MAP = 1;
const HMF_S64 = 2;
const HMF_STR = 3;
const HMF_BIN = 4;
const HMF_LIST = 5;
//JS Doesn't provide chr/ord.. wrapper functions for readaibility/consistency with the python example
function chr(i) {
return String.fromCharCode(i);
}
function ord(c) {
c = '' + c;
return c.charCodeAt(0);
}
class hmf_bin {}
class HTSPMessage {
hmf_type(f) {
if (Array.isArray(f)) return HMF_MAP;
else if (f instanceof hmf_bin) return HMF_BIN;
else if (typeof f == 'object') return HMF_LIST;
else if (typeof f == 'string') return HMF_STR;
else if (typeof f == 'number') return HMF_S64;
}
_binary_count(f) {
var ret = 0;
if (typeof f == 'string' || f instanceof hmf_bin || f instanceof Buffer) {
ret += f.length;
}
else if (typeof(f) == 'number') {
while (f) {
ret += 1;
f = f >> 8;
}
}
else if (typeof(f) == 'object' || Array.isArray(f)) {
ret += this.binary_count(f);
}
else throw new Error('invalid data type');
return ret;
}
binary_count(msg) {
var ret = 0;
var list = Array.isArray(msg)
for (var f in msg) {
ret += 6;
if (!list) {
//for objects add on the length of the key
ret += f.length;
//Python treats for ... in differently for objects and lists. for..in works like JS for..of for lists
//but the same for objects! So for js ignore this line
//f = msg[f]
}
ret += this._binary_count(msg[f]);
}
return ret;
}
int2bin(i) {
return chr(i >> 24 & 0xFF) + chr(i >> 16 & 0xFF) + chr(i >> 8 & 0xFF) + chr(i & 0xFF);
}
bin2int(d) {
return (ord(d[0]) << 24) + (ord(d[1]) << 16)+ (ord(d[2]) << 8) + ord(d[3]);
}
binary_write(msg) {
var ret = '';
var list = Array.isArray(msg);
for (var f in msg) {
var na = '';
if (!list) {
na = f;
//Python treats for ... in differently for objects and lists. for..in works like JS for..of for lists
//but the same for objects! ignore this line
//f = msg[f]
}
//set f to value rather than key
f = msg[f];
ret += chr(this.hmf_type(f));
ret += chr(na.length & 0xFF);
var l = this._binary_count(f);
ret += this.int2bin(l);
ret += na;
if (f instanceof Buffer) {
ret += f;
}
else if (f instanceof hmf_bin || typeof f == 'string') ret += f;
else if (typeof(f) == 'object' || Array.isArray(f)) ret += this.binary_write(f);
else if (typeof(f) == 'number') {
while (f) {
ret += chr(f & 0xFF);
f = f >> 8;
}
}
else throw new Error('invalid type');
}
return ret;
}
serialize(msg) {
var cnt = this.binary_count(msg);
return new Buffer(this.int2bin(cnt) + this.binary_write(msg), 'binary');
}
deserialize0(data, type = HMF_MAP) {
var isList = false;
var msg = {};
if (type == HMF_LIST) {
isList = true;
msg = [];
}
while (data.length > 5) {
//data type
var typ = ord(data[0]);
//length of the name
var nlen = ord(data[1]);
//length of the data for name/key in nlen
var dlen = this.bin2int(data.slice(2,6));
data = data.slice(6);
var name = data.slice(0, nlen);
data = data.slice(nlen);
var item = null;
if (typ == HMF_STR) {
item = data.slice(0, dlen).toString();
}
else if (typ == HMF_BIN) {
//not sure why there's a dummy wrapper for this in the python example..
// item = new Buffer(data.slice(0, dlen), 'binary');
item = data.slice(0, dlen);
}
else if (typ == HMF_S64) {
item = 0;
var i = dlen-1;
while (i >= 0) {
item = (item << 8) | ord(data[i]);
i = i -1;
}
}
else if (typ == HMF_LIST || type == HMF_MAP) {
item = this.deserialize0(data.slice(0, dlen), typ);
}
if (isList) msg.push(item);
else msg[name] = item;
data = data.slice(dlen);
}
return msg;
}
}
class HTSPClient {
constructor(host, port, user, pass, onConnect) {
this.username = user;
this.password = pass;
this.sock = new net.Socket();
this.sock.setEncoding('binary');
var self = this;
this.sock.connect(port, host, function() {
onConnect(self);
});
this.onData = function() {
};
this.sock.on('data', function(data) {
// console.log(data);
self.onData(data);
});
this.sock.on('error', function(err) {
console.log('ERRROR!!!');
console.log(err);
});
this.sock.on('close', function(had_error) {
console.log('closed'+had_error)
});
this.sock.on('end', function() {
console.log('sock end');
})
}
htsp_digest(chal) {
console.log(chal);
var generator = crypto.createHash('sha1');
generator.update(this.password + chal);
// generator.update(chal);
return generator.digest('binary');
}
send(func, args, callback, debug) {
if (!args) args = {};
args['method'] = func;
if (this.auth) {
args['username'] = this.username;
args['digest'] = this.htsp_digest(this.auth);
}
if (debug) {
console.log(args);
}
var s = new HTSPMessage().serialize(args);
this.sock.write(s, 'binary', callback);
}
hello(callback) {
var args = {
'htspversion' : 23,
'clientname' : 'node-htsp'
}
var self = this;
this.onData = function(data) {
self.recvHello(data, callback);
};
this.send('hello', args);
}
recvHello(data, callback) {
//The data comes back with 3 NULL chars and an odd symbol no idea why, need to remove them for this to work
//It seems to be a byte order mark but doesn't match utf-8, utf-16 etc.
//Is this added by the buffer? By the socket? Sent by the server? Could do with a proper explanation,
//but for now just remove the BOM and continue
data = data.slice(4);
var msg = new HTSPMessage().deserialize0(data);
this.auth = msg.challenge;
// console.log(msg.challenge);
this.authenticate();
//
}
authenticate() {
this.onData = function(data) {
console.log('auth response');
console.log(data);
};
this.send('authenticate', null, function() {
console.log('authwritten:');
}, true);
}
}
function onConnect(client) {
client.hello(function() {
//When the HELLO has been processed, authenticate
client.authenticate();
});
}
var client = new HTSPClient('media', 9982, 'media', 'media', onConnect);
/*
function helloAndAuthenticate(socket) {
var args = {
'htspversion' : 25,
'clientname' : 'node-htsp'
}
//Now
send(socket, 'hello', args, function(data) {
USER = 'media';
PASS = 'media';
});
socket.on('data', function(data) {
//The data comes back with 3 NULL chars and an odd symbol no idea why, need to remove them for this to work
//It seems to be a byte order mark but doesn't match utf-8, utf-16 etc.
//Is this added by the buffer? By the socket? Sent by the server? Could do with a proper explanation,
//but for now just remove the BOM and continue
data = data.slice(4);
var msg = deserialize0(data);
console.log(msg);
USER = 'media';
PASS = htsp_digest('media', 'media', msg.challenge);
send(socket, 'authenticate');
})
}
*/
@TRPB
Copy link
Author

TRPB commented Jul 31, 2016

When runs, TVHeadend's log shows:

2016-07-31 16:08:32.011 [   INFO] htsp: 192.168.0.2: Welcomed client software: node-htsp (HTSPv25)
2016-07-31 16:08:32.038 [   INFO] htsp: 192.168.0.2 [ node-htsp ]: Identified as user media
2016-07-31 16:08:32.038 [   INFO] htsp: 192.168.0.2 [ media | node-htsp ]: Disconnected

@rw251
Copy link

rw251 commented Oct 18, 2019

The 4 characters at the start are a 4 byte integer which gives the total length of the message to follow. Useful if the data sent comes in multiple packets.

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