Skip to content

Instantly share code, notes, and snippets.

@abdullah353
Forked from bearice/websocket.js
Created December 27, 2021 16:45
Show Gist options
  • Save abdullah353/e24e70707b67e04b6622624dd1dc4d97 to your computer and use it in GitHub Desktop.
Save abdullah353/e24e70707b67e04b6622624dd1dc4d97 to your computer and use it in GitHub Desktop.
My own (and dirty) implementation of WebSocket-Protocol-Version-8 on nodejs
var crypto = require('crypto');
var WSProtocolHandler = {
"8":{
handshake:function(req,socket,head,callback){
var key = req.headers["sec-websocket-key"];
if(!key)return false;
key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
var sha1 = crypto.createHash('sha1');
sha1.update(key);
var hash = sha1.digest('base64');
console.log(key,hash);
var response = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n";
response += "Server: WSAccel/1.0\r\n";
response += "Upgrade: WebSocket\r\n";
response += "Connection: Upgrade\r\n";
response += "Sec-WebSocket-Accept: "+hash+"\r\n\r\n";
//console.log(response);
socket.write(response,'utf8',callback);
return true;
},
decodePacket:function(buffer,callback){
if(buffer.length<2)return 0;
var offset = 0;
var fin,op,mask,len,key,data;
var tmp = buffer.readUInt8(offset++);
fin = tmp >> 7;
op = tmp & 0x0F;
tmp = buffer.readUInt8(offset++);
mask = tmp >>7;
len = tmp & 0x7F;
if(len==126){
if(buffer.length-offset<2)return 0;
len = buffer.readUInt16BE(offset);
offset += 2;
}else if(len == 127){
if(buffer.length-offset<8)return 0;
// Note: due to leak of 64bit integer support, read only low 32bits of length here.
offset += 4
len = buffer.readUInt32BE(offset);
offset += 4;
}
// console.info("fin : "+fin);
// console.info("op : "+op);
// console.info("mask : "+mask);
// console.info("length: "+len);
if(mask){
if(buffer.length-offset<4)return 0;
key = buffer.readUInt32BE(offset);
offset += 4;
}
if(buffer.length-offset<len)return 0;
buffer = buffer.slice(offset,offset+len);
if(key!=undefined){
this.mask(buffer,key);
}
callback(buffer);
return offset+len;
},
mask: function(buffer,key){
var offset = 0;
while(offset+4<buffer.length){
var val = buffer.readUInt32BE(offset);
val = val ^ key;
buffer.writeUInt32BE(val,offset);
offset += 4;
}
if(offset==buffer.length)return;
var tmpBuf = new Buffer(4);
tmpBuf.writeUInt32BE(key,0);
while(offset<buffer.length){
var val = buffer.readUInt8(offset);
key = tmpBuf.readUInt8(offset%4);
val = val ^ key;
buffer.writeUInt8(val,offset);
offset ++;
}
},
encodePacket:function(packet){
var len = 2;
if(packet.length>0xFFFF){
len += 8;
}else if(packet.length>126){
len += 2;
}
len += packet.length;
var ret = new Buffer(len);
len = 0;
ret.writeUInt8(0x81,len++);
if(packet.length>0xFFFF){
ret.writeUInt8(127,len++);
ret.writeUInt32BE(0,len);
ret.writeUInt32BE(packet.length,len+4);
len += 8;
}else if(packet.length>126){
ret.writeUInt8(126,len++);
ret.writeUInt16BE(packet.length,len);
len += 2;
}else{
ret.writeUInt8(packet.length,len++);
}
packet.copy(ret,len);
return ret;
},
},
};
function cpobj(dst,src){
for(k in src){dst[k]=src[k];}
return dst;
}
var WS = function(conf){
if(typeof(conf)=='function'){
conf = {
data: conf
};
}
var defaultConf = {
defaultVersion:"8",
};
conf = cpobj(defaultConf,conf);
return function(req,socket,head){
//console.log(req.headers);
var WSVer = req.headers["sec-websocket-version"];
WSVer = WSVer ? WSVer : conf.defaultVersion;
//console.log(WSVer);
function Handler(){}
Handler.prototype = WSProtocolHandler[WSVer];
var handler = new Handler();
var buffer = null;
function sendPacket(packet){
packet = handler.encodePacket(packet);
return socket.write(packet);
}
function userCallback(){
var arr = Array.prototype.slice.call(arguments);
var cb = arr.shift();
if(cb && typeof(cb)=='function'){
try{
cb.apply(conf,arr);
}catch(e){
console.error("error:",e);
}
}
}
function onData(data){
// console.info("data",data.length);
function packetHandler(data){
// console.info("packet",data);
userCallback(conf.data,data);
}
if(buffer){
var newbuf = new Buffer(buffer.length+data.length);
buffer.copy(newbuf);
data.copy(newbuf,buffer.length);
buffer = newbuf;
}else{
buffer = data;
}
var ret;
while((ret = handler.decodePacket(buffer,packetHandler))>0){
buffer = buffer.slice(ret);
};
}
function onError(err){
//console.info("error");
userCallback(conf.error,err);
socket.destroy();
}
function onClose(){
//console.info("close");
userCallback(conf.close);
}
function initSocket(){
//console.info("init socket");
socket.on('data' ,onData);
socket.on('error',onError);
socket.on('end' ,onClose);
socket.on('close',onClose);
//console.log(socket);
}
conf.write = sendPacket;
if(handler.handshake(req,socket,head,initSocket) == false){
onError("handshake failed");
return;
}
};
};
exports.createWebSocket = WS;
/*
* DEMO USAGE
var http = require('http'),ws=require('./websocket');
var server = http.createServer();
server.on('upgrade',ws.createWebSocket({
error:function(e){
console.log('error:'+e);
},
close:function(){
console.log("closed");
},
data:function(data){
console.log('data:'+data);
this.write(data);
}
});
server.listen(8080);
console.log('Server running...');
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment