Last active
June 9, 2024 00:59
-
-
Save littletsu/dac6c012c02772f7091425aa14002c6c to your computer and use it in GitHub Desktop.
Really simple probably non-standard websocket server in nodejs using tcp server
This file contains hidden or 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
import net from 'net'; | |
import crypto from 'crypto'; | |
const port = 5000; | |
const sha1 = function(str) { | |
const shasum = crypto.createHash('sha1') | |
shasum.update(str) | |
return shasum.digest().toString("base64") | |
} | |
const STATUS = { | |
WAITING: 0, | |
CONNECTED: 1, | |
} | |
const readHeaderListIncludes = (value, str) => { | |
let elements = value.split(","); | |
for(let i = 0; i < elements.length; i++) { | |
let element = elements[i]; | |
if(element[0] == " ") { | |
element = element.substring(1) | |
} | |
if(element[element.length-1] == " ") { | |
element = element.substring(0, element.length-1) | |
} | |
if(element == str) { | |
return true; | |
} | |
} | |
} | |
const server = net.createServer((socket) => { | |
console.log("Client connected"); | |
let status = STATUS.WAITING; | |
const writeHTTP = (response) => { | |
socket.write(response.split("\r\n").join("\n").split("\n").join("\r\n")); | |
} | |
const endHTTP = (err) => { | |
writeHTTP( | |
`HTTP/1.1 400 Bad Request | |
Connection: Close | |
Access-Control-Allow-Origin: * | |
Cross-Origin-Opener-Policy: same-origin | |
Cross-Origin-Embedder-Policy: require-corp | |
Cache-Control: max-age=15 | |
Sec-Websocket-Version: 13 | |
Awawa: ${err ?? "Bad Request"} | |
`) | |
socket.end(); | |
} | |
const sendWS = (op, payload) => { | |
let fin = 0b1; | |
let rsv1 = 0b0; | |
let rsv2 = 0b0; | |
let rsv3 = 0b0; | |
let first = ((fin << 7) | (rsv1 << 6) | (rsv2 << 5) | (rsv3 << 4) | (op)) | |
if(payload.length > 125) { | |
console.log(`Unimplemented payload length ${payload.length}`); | |
return; | |
} | |
socket.write( | |
Buffer.concat( | |
[ | |
Buffer.from([first, payload.length]), | |
payload | |
] | |
) | |
); | |
} | |
const textWS = (text) => { | |
sendWS(0x1, Buffer.from(text)); | |
} | |
const upgradeConnection = (data) => { | |
const strData = data.toString(); | |
console.log(`Received: ${strData}`); | |
const request = strData.split("\r\n"); | |
const [method, path, protocol] = request[0].split(" "); | |
if(method !== "GET" || path !== "/ws" || protocol !== "HTTP/1.1") return endHTTP("Invalid route or method"); | |
const headers = {} | |
for(let i = 1; i < request.length; i++) { | |
let header = request[i].split(": "); | |
headers[header[0]] = header[1]; | |
} | |
console.log(headers); | |
if(headers["Upgrade"] === undefined || headers["Upgrade"] !== "websocket") { | |
return endHTTP("bad upgrade"); | |
} | |
if(headers["Connection"] === undefined || !readHeaderListIncludes(headers["Connection"], "Upgrade")) { | |
return endHTTP("bad connection"); | |
} | |
if(headers["Sec-WebSocket-Version"] === undefined || headers["Sec-WebSocket-Version"] !== "13") { | |
return endHTTP("bad version"); | |
} | |
if(headers["Sec-WebSocket-Key"] === undefined) { | |
return endHTTP("no key"); | |
} | |
const accept = sha1(headers["Sec-WebSocket-Key"]+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); | |
writeHTTP( | |
`HTTP/1.1 101 Switching Protocols | |
Upgrade: websocket | |
Connection: Upgrade | |
Sec-WebSocket-Accept: ${accept}\r\n\r\n`); | |
status = STATUS.CONNECTED; | |
} | |
socket.on("data", (data) => { | |
if(status === STATUS.WAITING) return upgradeConnection(data); | |
let first = data[0] | |
let fin = first & 0b10000000; | |
let rsv1 = first & 0b01000000; | |
let rsv2 = first & 0b00100000; | |
let rsv3 = first & 0b00010000; | |
let opcode = first & 0b00001111; | |
let second = data[1]; | |
let masked = second & 0b10000000; | |
let len = second & 0b01111111; | |
if(len > 125) { | |
console.log(`Unimplemented client payload length ${len}`) | |
return; | |
} | |
let payload = data.subarray(2); | |
if(masked != 0) { | |
let mask = data.subarray(2, 6); | |
payload = data.subarray(6); | |
let decoded = []; | |
for(let i = 0; i < payload.length; i++) { | |
decoded[i] = payload[i] ^ mask[i % mask.length]; | |
} | |
payload = decoded; | |
} else { | |
socket.end(); | |
return; | |
} | |
console.log(`FIN: ${fin != 0}, RSV: ${rsv1 != 0},${rsv2 != 0},${rsv3 != 0}, OP: ${opcode}, MASKED: ${masked != 0}, LEN: ${len}, PAYLOAD: `, payload) | |
if(opcode == 0x1) { | |
const decoded = String.fromCharCode(...payload); | |
console.log(`Decoded: ${decoded}`) | |
if(decoded == "hi") textWS("Hi bro!"); | |
} | |
}); | |
socket.on("end", () => { | |
console.log("Client disconnected"); | |
}); | |
socket.on("error", (error) => { | |
console.log(`Socket Error: ${error.message}`); | |
}); | |
}); | |
server.on("error", (error) => { | |
console.log(`Server Error: ${error.message}`); | |
}); | |
server.listen(port, () => { | |
console.log(`TCP socket server is running on port: ${port}`); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment