Compare commits
No commits in common. "7fd4c589dfa87b743047c617bcb036f01b48c0d6" and "641a978cbec61ba26743fa4851a16efe370eac98" have entirely different histories.
7fd4c589df
...
641a978cbe
5 changed files with 22 additions and 188 deletions
|
@ -1,13 +1,3 @@
|
||||||
const http = require('http');
|
|
||||||
|
|
||||||
const Wormhole = require('../index');
|
const Wormhole = require('../index');
|
||||||
|
|
||||||
const httpServer = http.createServer();
|
const wormhole = new Wormhole({ urls: [ '/hello' ] });
|
||||||
|
|
||||||
const wormhole = new Wormhole({ urls: [ '/hello' ], httpServer });
|
|
||||||
|
|
||||||
wormhole.on('connect', ({ socket, accept, reject }) => {
|
|
||||||
accept();
|
|
||||||
});
|
|
||||||
|
|
||||||
httpServer.listen(8080);
|
|
65
index.js
65
index.js
|
@ -1,4 +1,4 @@
|
||||||
const EventEmitter = require('events');
|
const http = require('http');
|
||||||
|
|
||||||
const { createLog } = require('./lib/logger');
|
const { createLog } = require('./lib/logger');
|
||||||
const handshake = require('./lib/handshake');
|
const handshake = require('./lib/handshake');
|
||||||
|
@ -7,33 +7,22 @@ const Socket = require('./lib/Socket');
|
||||||
|
|
||||||
const handshakeLog = createLog([ 'Wormhole', 'Handshake' ]);
|
const handshakeLog = createLog([ 'Wormhole', 'Handshake' ]);
|
||||||
|
|
||||||
class Wormhole extends EventEmitter {
|
class Wormhole {
|
||||||
constructor({ urls=[ '/bruh' ], httpServer }) {
|
constructor({ urls=[ '/bruh' ], port=8080 }) {
|
||||||
super();
|
|
||||||
|
|
||||||
this._urls = urls;
|
this._urls = urls;
|
||||||
this._httpServer = httpServer;
|
this._port = port;
|
||||||
|
|
||||||
this._sockets = [];
|
this._httpServer = http.createServer((req, res) => {
|
||||||
|
|
||||||
this._httpServer.on('request', ((req, res) => {
|
|
||||||
if (req.method === 'GET' && req.url && this._urls.includes(req.url)) {
|
if (req.method === 'GET' && req.url && this._urls.includes(req.url)) {
|
||||||
handshakeLog(`Got connection request to ${req.url}`);
|
handshakeLog(`Got connection request to ${req.url} on port ${this._port}`);
|
||||||
|
|
||||||
let socket = new Socket({ socket: res.socket, initalState: constants.states.CONNECTING });
|
|
||||||
|
|
||||||
const failConnection = (status=400) => {
|
const failConnection = (status=400) => {
|
||||||
socket._setConnectionState(constants.states.CLOSING);
|
|
||||||
|
|
||||||
res.writeHead(status);
|
res.writeHead(status);
|
||||||
res.end();
|
res.end();
|
||||||
|
|
||||||
socket._setConnectionState(constants.states.CLOSED);
|
|
||||||
|
|
||||||
console.trace();
|
console.trace();
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: check origin header
|
|
||||||
const websocketKey = req.headers['sec-websocket-key'];
|
const websocketKey = req.headers['sec-websocket-key'];
|
||||||
const upgradeHeader = req.headers['upgrade'];
|
const upgradeHeader = req.headers['upgrade'];
|
||||||
const websocketVersion = req.headers['sec-websocket-version'];
|
const websocketVersion = req.headers['sec-websocket-version'];
|
||||||
|
@ -43,42 +32,26 @@ class Wormhole extends EventEmitter {
|
||||||
|
|
||||||
const websocketAccept = handshake.generateWebsocketAcceptValue(websocketKey);
|
const websocketAccept = handshake.generateWebsocketAcceptValue(websocketKey);
|
||||||
|
|
||||||
|
handshakeLog(websocketKey, websocketAccept);
|
||||||
|
|
||||||
if (websocketAccept) {
|
if (websocketAccept) {
|
||||||
const accept = () => {
|
res.writeHead(101, {
|
||||||
try {
|
'Upgrade': 'websocket',
|
||||||
socket._setAccepted(true);
|
'Connection': 'Upgrade',
|
||||||
} catch(e) {
|
'Sec-WebSocket-Accept': websocketAccept
|
||||||
throw new Error('Tried to set socket fate (wether it is accept or not) more than once. Check if there are multiple listeners for the "connect" event or if you are somehow calling accept or reject multiple times.');
|
});
|
||||||
}
|
res.end();
|
||||||
|
|
||||||
res.writeHead(101, {
|
|
||||||
'Upgrade': 'websocket',
|
|
||||||
'Connection': 'Upgrade',
|
|
||||||
'Sec-WebSocket-Accept': websocketAccept
|
|
||||||
});
|
|
||||||
res.end();
|
|
||||||
|
|
||||||
socket._setConnectionState(constants.states.OPEN);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const reject = (status=403) => {
|
const socket = new Socket({ socket: res.socket, initalState: 'CONNECTED' });
|
||||||
try {
|
|
||||||
socket._setAccepted(false);
|
|
||||||
} catch(e) {
|
|
||||||
throw new Error('Tried to set socket fate (wether it is accept or not) more than once. Check if there are multiple listeners for the "connect" event or if you are somehow calling accept or reject multiple times.');
|
|
||||||
}
|
|
||||||
|
|
||||||
failConnection(status);
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.emit('connect', { socket, accept, reject });
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return failConnection();
|
return failConnection();
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
|
|
||||||
|
this._httpServer.listen(port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
101
lib/Parser.js
101
lib/Parser.js
|
@ -1,101 +0,0 @@
|
||||||
class WebsocketFrame {
|
|
||||||
constructor() {
|
|
||||||
// Just defining the structure of the WebsocketFrame,
|
|
||||||
// I could actually just use a normal object but I
|
|
||||||
// might need to add some methods to this in the future
|
|
||||||
// so it's good enough
|
|
||||||
|
|
||||||
// 1 byte - first byte
|
|
||||||
this.FIN = undefined;
|
|
||||||
this.RSVx = undefined;
|
|
||||||
this.Opcode = undefined;
|
|
||||||
|
|
||||||
// 1 byte - second byte
|
|
||||||
this.MASK = undefined;
|
|
||||||
this.PayloadLen = undefined;
|
|
||||||
|
|
||||||
// 4 bytes - masking key
|
|
||||||
this.MaskingKey = undefined;
|
|
||||||
|
|
||||||
// (Extension Data + Application Data[PayloadLen]) (kind of) - payload data
|
|
||||||
// TODO: Separate extension data from application data
|
|
||||||
this.PayloadData = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseWebsocketFrame = (data) => {
|
|
||||||
const firstByte = data.getUint8(0);
|
|
||||||
const secondByte = data.getUint8(1);
|
|
||||||
|
|
||||||
const frame = new WebsocketFrame();
|
|
||||||
|
|
||||||
// 1 byte - first byte
|
|
||||||
frame.FIN = (firstByte & 0x01); // 0x01[0b10000000] - Get most significant bit (FIN)
|
|
||||||
frame.RSVx = (firstByte & 0x70) // 0x70[0b01110000] - Get the 3 bits after the most significant bit (RSVx)
|
|
||||||
frame.Opcode = (firstByte & 0x0F) // 0xF[0b00001111] - Get the last 4 bits (Opcode)
|
|
||||||
|
|
||||||
// 1 byte - second byte
|
|
||||||
frame.MASK = (secondByte & 0x01) // 0x01[0b10000000] - Get most significant bit (MASK)
|
|
||||||
frame.PayloadLen = (secondByte & 0x7F) // 0x7F[0b01111111] - Get last 7 bits (Payload len)
|
|
||||||
|
|
||||||
let maskingKeyOffset = 2; // By default, theres a 2 byte offset. We will modify this in the cases below.
|
|
||||||
|
|
||||||
// Handle Payload len cases and set the masking key offset
|
|
||||||
if (frame.PayloadLen === 126) {
|
|
||||||
frame.PayloadLen = data.getUint16(2);
|
|
||||||
maskingKeyOffset = 4; // 4 byte offset, because we also read 2 bytes with getUint16 above (2 bytes (normal size) + 2 bytes)
|
|
||||||
} else if (frame.PayloadLen === 127) {
|
|
||||||
frame.PayloadLen = data.getBigUint64(2);
|
|
||||||
maskingKeyOffset = 10; // 10 byte offset, because we also read 8 bytes with getBigUint64 above (2 bytes (normal size) + 8 bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
const maskingKeyEnd = maskingKeyOffset + 4; // (4 bytes because it is a 32 bit value)
|
|
||||||
if (frame.MASK) frame.MaskingKey = data.buffer.slice(maskingKeyOffset, maskingKeyEnd); // Create a new buffer starting at the masking key offset and ending at the masking key end (duh).
|
|
||||||
|
|
||||||
// TODO: Separate extension data from application data
|
|
||||||
frame.PayloadData = data.buffer.slice(maskingKeyEnd, maskingKeyEnd + frame.PayloadLen); // Create a new buffer that starts at the end of the masking key and ends at (the end of the masking key + payload length)
|
|
||||||
|
|
||||||
// TODO: implement unmasked string decoding
|
|
||||||
if (frame.MASK) {
|
|
||||||
switch (frame.Opcode) {
|
|
||||||
case 1: { // Denotes a text frame
|
|
||||||
const payloadDataView = new DataView(frame.PayloadData);
|
|
||||||
const maskingKeyView = new DataView(frame.MaskingKey);
|
|
||||||
|
|
||||||
let decoded = '';
|
|
||||||
|
|
||||||
for (let i = 0; i < frame.PayloadData.byteLength; i++) {
|
|
||||||
decoded += (String.fromCharCode(payloadDataView.getUint8(i) ^ maskingKeyView.getUint8(i % 4)));
|
|
||||||
}
|
|
||||||
|
|
||||||
frame.DecodedPayloadData = decoded;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 2: { // Denotes a binary frame
|
|
||||||
// TODO: untested
|
|
||||||
const payloadDataView = new DataView(frame.PayloadData);
|
|
||||||
const maskingKeyView = new DataView(frame.MaskingKey);
|
|
||||||
|
|
||||||
const decoded = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < frame.PayloadData.byteLength; i++) {
|
|
||||||
decoded.push(payloadDataView.getUint8(i) ^ maskingKeyView.getUint8(i % 4));
|
|
||||||
}
|
|
||||||
|
|
||||||
frame.DecodedPayloadData = decoded;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
WebsocketFrame,
|
|
||||||
|
|
||||||
parseWebsocketFrame
|
|
||||||
};
|
|
|
@ -1,34 +1,12 @@
|
||||||
const constants = require("./constants");
|
|
||||||
const parser = require('./Parser');
|
|
||||||
|
|
||||||
class Socket {
|
class Socket {
|
||||||
constructor({ initialState='CONNECTING', socket }) {
|
constructor({ initialState='CONNECTING', socket }) {
|
||||||
this._state = initialState;
|
this._state = initialState;
|
||||||
this._socket = socket;
|
this._socket = socket;
|
||||||
this._accepted = false;
|
|
||||||
this._fateDecided = false; // Wether the decision to accept or reject the socket was made
|
|
||||||
|
|
||||||
this._socket.on('data', (e) => {
|
this._socket.on('data', (e) => {
|
||||||
if (this._state !== constants.states.OPEN) return;
|
console.log(e.toString());
|
||||||
|
|
||||||
this._decodePayload(e.buffer);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Socket.prototype._decodePayload = function(payload) {
|
|
||||||
console.log(parser.parseWebsocketFrame(new DataView(payload)));
|
|
||||||
};
|
|
||||||
|
|
||||||
Socket.prototype._setConnectionState = function(state) {
|
|
||||||
this._state = state;
|
|
||||||
};
|
|
||||||
|
|
||||||
Socket.prototype._setAccepted = function(state) {
|
|
||||||
if (this._fateDecided) throw new Error('Tried to decide fate (wether socket is accepted or not) more than 1 time');
|
|
||||||
|
|
||||||
this._fateDecided = true;
|
|
||||||
this._accepted = state;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Socket;
|
module.exports = Socket;
|
|
@ -1,11 +1,5 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
handshakeGUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
|
handshakeGUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
|
||||||
upgradeHeaderRequirement: 'websocket',
|
upgradeHeaderRequirement: 'websocket',
|
||||||
websocketVersionRequirement: '13',
|
websocketVersionRequirement: '13'
|
||||||
states: {
|
|
||||||
CONNECTING: 'CONNECTING',
|
|
||||||
OPEN: 'OPEN',
|
|
||||||
CLOSING: 'CLOSING',
|
|
||||||
CLOSED: 'CLOSED'
|
|
||||||
}
|
|
||||||
}
|
}
|
Loading…
Reference in a new issue