149 lines
3.1 KiB
JavaScript
149 lines
3.1 KiB
JavaScript
const net = require('net');
|
|
const EventEmitter = require('events');
|
|
const NodeRSA = require('node-rsa');
|
|
const log = require('signale').scope('stp');
|
|
|
|
module.exports.createServer = function(keys, onConnect) {
|
|
const server = new Server(keys);
|
|
server.on('connection', onConnect);
|
|
return server;
|
|
// return 5;
|
|
}
|
|
|
|
module.exports.connect = function(identity, port, ip) {
|
|
const client = net.connect(port, ip);
|
|
return new STPSocket(client, identity);
|
|
}
|
|
|
|
class Server extends EventEmitter {
|
|
tcpServer;
|
|
identity;
|
|
|
|
constructor(identity) {
|
|
super();
|
|
this.identity = identity;
|
|
this.tcpServer = net.createServer(this.tcpConnectClient.bind(this));
|
|
}
|
|
|
|
tcpConnectClient(tcpSocket) {
|
|
const socket = new STPSocket(tcpSocket, this.identity);
|
|
socket.on('ready', () => {
|
|
this.emit('connection', socket);
|
|
})
|
|
}
|
|
|
|
listen(...args) {
|
|
this.tcpServer.listen(...args);
|
|
}
|
|
}
|
|
|
|
class STPSocket extends EventEmitter {
|
|
tcpSocket;
|
|
readyState = 0;
|
|
buffer = '';
|
|
externalIdentity;
|
|
|
|
get remoteAddress() {
|
|
return this.tcpSocket.remoteAddress;
|
|
}
|
|
|
|
get remoteIdentity() {
|
|
return this.externalIdentity.exportKey('pkcs8-public-pem');
|
|
}
|
|
|
|
get open() {
|
|
return this.tcpSocket.readyState === 'open'
|
|
}
|
|
|
|
constructor(tcpSocket, identity) {
|
|
super();
|
|
this.tcpSocket = tcpSocket;
|
|
this.identity = identity;
|
|
if(this.open) this.handshake();
|
|
else this.tcpSocket.on('connect', this.handshake.bind(this));
|
|
|
|
this.tcpSocket.on('data', this.data.bind(this));
|
|
this.tcpSocket.on('error', (...args) => this.emit('error', ...args));
|
|
this.tcpSocket.on('close', (...args) => this.emit('close', ...args));
|
|
}
|
|
|
|
data(evt) {
|
|
// log.debug(evt.toString());
|
|
this.buffer += evt.toString();
|
|
this.processBuffer();
|
|
}
|
|
|
|
processBuffer() {
|
|
const parts = this.buffer.split(/(\x02[^\x02\x03]*\x03)/g);
|
|
this.buffer = '';
|
|
for(const message of parts) {
|
|
if(message.endsWith('\x03')) {
|
|
const obj = JSON.parse(message.substr(1, message.length - 2));
|
|
this.processMessage(obj);
|
|
} else {
|
|
this.buffer += message;
|
|
}
|
|
}
|
|
}
|
|
|
|
processMessage(obj) {
|
|
switch(obj.cmd) {
|
|
case 'KEY': {
|
|
if(this.readyState === 0) {
|
|
// log.debug('registering external key');
|
|
this.externalIdentity = new NodeRSA();
|
|
this.externalIdentity.importKey(obj.data.key, 'pkcs8-public-pem');
|
|
this.tcpSocket.write(new AckPacket().toBuffer());
|
|
this.readyState = 1;
|
|
}
|
|
break;
|
|
}
|
|
case 'ACK': {
|
|
if(this.readyState === 1) {
|
|
this.readyState = 2;
|
|
// log.debug('socket ready!');
|
|
this.emit('ready');
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
handshake() {
|
|
// log.debug('begin handshake on socket');
|
|
const pk = this.identity.publicKey;
|
|
const packet = new KeyExchangePacket(pk);
|
|
const buffer = packet.toBuffer();
|
|
this.tcpSocket.write(buffer);
|
|
}
|
|
}
|
|
|
|
class STPPacket {
|
|
cmd = 'NOOP';
|
|
data = {};
|
|
meta = {};
|
|
|
|
toBuffer() {
|
|
return Buffer.from(`\x02${JSON.stringify({
|
|
cmd: this.cmd,
|
|
data: this.data,
|
|
meta: this.meta
|
|
})}\x03`);
|
|
}
|
|
}
|
|
|
|
class KeyExchangePacket extends STPPacket {
|
|
constructor(key, type = 'pkcs8-pem') {
|
|
super();
|
|
this.cmd = 'KEY';
|
|
this.data.key = key;
|
|
this.meta.type = type;
|
|
}
|
|
}
|
|
|
|
class AckPacket extends STPPacket {
|
|
constructor() {
|
|
super();
|
|
this.cmd = 'ACK';
|
|
}
|
|
} |