Skip to content

Commit

Permalink
STUN + UID lookup handling
Browse files Browse the repository at this point in the history
  • Loading branch information
fbertone committed Mar 25, 2017
1 parent 3d4ebdc commit a8565b6
Show file tree
Hide file tree
Showing 6 changed files with 309 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
"extends": "standard",
"plugins": [
"standard",
"promise"
]
};
77 changes: 77 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const dgram = require('dgram')
const EventEmitter = require('events')
const util = require('util')

const camClient = require('./lib/camClient')
const cloudClient = require('./lib/cloudClient')

const client = function client (opts = {}, udpSocket) {
let socket
let servers = opts.servers ? opts.servers : []
let uid = opts.uid ? opts.uid : ''
let camAddresses = opts.camDirectAddresses ? opts.camDirectAddresses : []
let emitter = new EventEmitter()

if (udpSocket) {
socket = udpSocket
} else {
socket = dgram.createSocket('udp4')
socket.bind()
}

socket.on('message',
function (msg, rinfo) { // check if message is from a server or the camera
if (addressExists(servers, {host: rinfo.address, port: rinfo.port})) {
cloudClient.parseMessage(msg, (type, info) => { emitter.emit(type, info) })
} else if (addressExists(camAddresses, {host: rinfo.address, port: rinfo.port})) {
camClient.parseMessage(msg, (type, info) => { emitter.emit(type, info) })
} else { // unknown sender

}
})

const addServer = function addServer (address) {
if (!addressExists(servers, address)) {
servers.push(address)
}
}

const setUid = function setUid (newUid) {
uid = newUid
}

const sendSTUNRequest = function sendSTUNRequest () {
servers.forEach((address) => { cloudClient.sendSTUN(socket, address) })
}

const lookupUid = function lookupUid (uidToCheck) {
if (!uidToCheck) {
uidToCheck = uid
}
servers.forEach((address) => { cloudClient.lookupUid(socket, address, uidToCheck) })
}

const on = function on (ev, cb) {
emitter.addListener(ev, cb)
}

return {
addServer,
setUid,
sendSTUNRequest,
lookupUid,
on
}
}

// TODO check EventEmitter

util.inherits(client, EventEmitter)

const addressExists = function addressExists (arr, address) {
return arr.some(function (el) {
return (el.host === address.host && el.port === address.port)
})
}

module.exports.client = client
64 changes: 64 additions & 0 deletions lib/camClient.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const utils = require('./utils')

const responseHeaders = {
end: Buffer.from([0xF1, 0xF0]),
pong: Buffer.from([0xF1, 0xE1]),
ping: Buffer.from([0xF1, 0xE0]),
camId: Buffer.from([0xF1, 0x42]),
ack: Buffer.from([0xF1, 0xD1]),
http: Buffer.from([0xF1, 0xD0])
}

const commands = {
ping: Buffer.from([0xF1, 0xE0]),
pong: Buffer.from([0xF1, 0xE1]),
checkCam: Buffer.from([0xF1, 0x41]),
get: Buffer.from([0xF1, 0xD0]),
ack: Buffer.from([0xF1, 0xD1]),
end: Buffer.from([0xF1, 0xF0])
}

const openSession = function openSession (socket, address, uid) {
let idToSend = utils.uid564toBuffer(uid)
utils.sendMessage(socket, address, commands.checkCam, idToSend)
}

const sendPing = function sendPing (socket, address) {
utils.sendMessage(socket, address, commands.ping, null)
}

const sendPong = function sendPong (socket, address) {
utils.sendMessage(socket, address, commands.pong, null)
}

const closeSession = function closeSession (socket, address) {
utils.sendMessage(socket, address, commands.end, null)
}

const parseMessage = function parseMessage (msg, cb) {
let header = Buffer.allocUnsafe(2)
msg.copy(header, 0, 0, 2)

if (header.equals(responseHeaders.ping)) {
return cb('pingpong', {})
} else if (header.equals(responseHeaders.pong)) {
return cb('pingpong', {})
} else if (header.equals(responseHeaders.end)) {
return cb('close', {})
} else if (header.equals(responseHeaders.camId)) {
// TODO parse ID
return cb('confirmed', {})
} else if (header.equals(responseHeaders.ack)) {
return cb('ack', {})
} else if (header.equals(responseHeaders.http)) {
return cb('http', {})
} else {
return cb('unknownMsg', {header: 'unknown', hex: msg.toString('hex')})
}
}

module.exports.parseMessage = openSession
module.exports.parseMessage = sendPing
module.exports.parseMessage = sendPong
module.exports.parseMessage = closeSession
module.exports.parseMessage = parseMessage
55 changes: 55 additions & 0 deletions lib/cloudClient.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const utils = require('./utils')

const commands = {
stun: Buffer.from([0xF1, 0x00]),
lookup: Buffer.from([0xF1, 0x20])
}

const responseHeaders = {
stun: Buffer.from([0xF1, 0x01]),
lookupRESP: Buffer.from([0xF1, 0x21]),
lookupAddr: Buffer.from([0xF1, 0x40])
}

const sendSTUN = function (socket, address) {
utils.sendMessage(socket, address, commands.stun)
}

const lookupUid = function lookupUid (socket, address, uid) {
let newUid = utils.uid564toBuffer(uid)
let payload = Buffer.concat([newUid, Buffer.from([0x00, 0x00, Math.floor(socket.address().port / 256), socket.address().port % 256, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])])

utils.sendMessage(socket, address, commands.lookup, payload)
}

const parseMessage = function parseMessage (msg, cb) {
let header = Buffer.allocUnsafe(2)
msg.copy(header, 0, 0, 2)

if (header.equals(responseHeaders.stun)) {
let port = msg[7] * 256 + msg[6]
let ip = msg[11] + '.' + msg[10] + '.' + msg[9] + '.' + msg[8]
return cb('stun', {address: {host: ip, port: port}})
} else if (header.equals(responseHeaders.lookupRESP)) {
if (msg.equals(Buffer.from([0xF1, 0x21, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00]))) {
return cb('lookupACK', {})
} else if (msg.equals(Buffer.from([0xF1, 0x21, 0x00, 0x04, 0xFE, 0x00, 0x00, 0x00]))) {
return cb('lookup', {isValid: true, isOnline: false})
} else if (msg.equals(Buffer.from([0xF1, 0x21, 0x00, 0x04, 0xFF, 0x00, 0x00, 0x00]))) {
return cb('lookup', {isValid: false, isOnline: false})
} else {
return cb('unknownMsg', {header: 'lookup'})
}
} else if (header.equals(responseHeaders.lookupAddr)) {
// logger.log('info', 'lookupAddr')
let port = msg[7] * 256 + msg[6]
let ip = msg[11] + '.' + msg[10] + '.' + msg[9] + '.' + msg[8]
return cb('lookup', {host: ip, port: port})
} else {
return cb('unknownMsg', {header: 'unknown', hex: msg.toString('hex')})
}
}

module.exports.sendSTUN = sendSTUN
module.exports.lookupUid = lookupUid
module.exports.parseMessage = parseMessage
69 changes: 69 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const sendMessage = function sendMessage (socket, address, msgID, payload, cb = () => {}) {
if (!payload) payload = Buffer.from([])
let payloadLen = Buffer.from([Math.floor(payload.length / 256), payload.length % 256])

let message = Buffer.concat([msgID, payloadLen, payload], 4 + payload.length)

sendPackage(socket, address, message, function (err, bytes) {
if (err) {
return cb(err)
} else {
return cb(null, bytes)
}
})
}

const sendPackage = function sendPackage (socket, address, pkg, cb = () => {}) {
socket.send(pkg, address.port, address.host, function (err, bytes) {
if (err) {
return cb(err)
} else {
return cb(null, bytes)
}
})
}

const uid564toBuffer = function uid564toBuffer (uid) {
let literalUID = Buffer.from(uid.substr(0, 4))

// var newUID = Buffer.from([uid[0], uid[1], uid[2], uid[3], 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x55, 0x5f, uid[10], uid[11], uid[12], uid[13], uid[14]])

let digit = Number(uid.substr(4, 6))

let str = digit.toString(16)
let first = 0
let second = 0
let third = 0

switch (str.length) {
case 1:
str = '00000' + str
break
case 2:
str = '0000' + str
break
case 3:
str = '000' + str
break
case 4:
str = '00' + str
break
case 5:
str = '0' + str
break
}

first = parseInt(str.substr(0, 2), 16)
second = parseInt(str.substr(2, 2), 16)
third = parseInt(str.substr(4, 2), 16)

let numeric = [0x00, 0x00, 0x00, 0x00, 0x00, first, second, third]
let numericUID = Buffer.from(numeric)
let randomUID = Buffer.from(uid.substr(10, 5))
let newUID = Buffer.concat([literalUID, numericUID, randomUID, Buffer.from([0x00, 0x00, 0x00])], 20)
return newUID
}

module.exports.sendMessage = sendMessage
module.exports.sendPackage = sendPackage
module.exports.uid564toBuffer = uid564toBuffer
37 changes: 37 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "lib32100",
"version": "1.0.0",
"description": "Library implementing port 32100 UDP Cloud protocol",
"main": "index.js",
"scripts": {
"test": "./node_modules/.bin/eslint *.js lib/*.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/fbertone/lib32100.git"
},
"keywords": [
"cloud",
"32100",
"P2P",
"cam",
"camera",
"IP",
"UDP",
"IoT"
],
"author": "Fabrizio Bertone",
"license": "MIT",
"bugs": {
"url": "https://github.com/fbertone/lib32100/issues"
},
"homepage": "https://github.com/fbertone/lib32100#readme",
"devDependencies": {
"eslint": "^3.18.0",
"eslint-config-standard": "^7.1.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-node": "^4.2.1",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-standard": "^2.1.1"
}
}

0 comments on commit a8565b6

Please sign in to comment.