From ea96e4decaa23fc8ddfb528d4851751ec9496490 Mon Sep 17 00:00:00 2001 From: Mike Buland Date: Wed, 18 Oct 2017 07:49:59 -0700 Subject: I think this version works. It would be really cool to add more features, like pre-websocket negotiation and callbacks for serving web content, etc. --- src/unstable/protocolwebsocket.cpp | 300 +++++++++++++++++++++++++++++++++++++ src/unstable/protocolwebsocket.h | 64 ++++++++ 2 files changed, 364 insertions(+) create mode 100644 src/unstable/protocolwebsocket.cpp create mode 100644 src/unstable/protocolwebsocket.h (limited to 'src') diff --git a/src/unstable/protocolwebsocket.cpp b/src/unstable/protocolwebsocket.cpp new file mode 100644 index 0000000..9200904 --- /dev/null +++ b/src/unstable/protocolwebsocket.cpp @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2007-2014 Xagasoft, All rights reserved. + * + * This file is part of the libbu++ library and is released under the + * terms of the license contained in the file LICENSE. + */ + +#include "bu/config.h" +#include "bu/protocolwebsocket.h" + +#include "bu/sio.h" +#include "bu/client.h" + +#include "bu/membuf.h" +#include "bu/base64.h" +#include "bu/sha1.h" +#include "bu/json.h" + +#include + +Bu::ProtocolWebSocket::ProtocolWebSocket() : + eStatus( stProtoId ) +{ +} + +Bu::ProtocolWebSocket::~ProtocolWebSocket() +{ +} + +void Bu::ProtocolWebSocket::onNewConnection( Bu::Client *pClient ) +{ + this->pClient = pClient; +} + +void Bu::ProtocolWebSocket::onNewData( Bu::Client * /*pClient*/ ) +{ + for(;;) + { + switch( eStatus ) + { + case stProtoId: + if( !stateProtoId() ) + return; + break; + + case stHandshake: + if( !stateHandshake() ) + return; + break; + + case stReady: + if( !parseMessage() ) + return; + break; + } + } +} + +void Bu::ProtocolWebSocket::writeMessage( const Bu::String &sData, + Bu::ProtocolWebSocket::Operation eOp ) +{ + uint8_t cHeader[32]; + //uint8_t *cMask; + memset( cHeader, 0, 32 ); + + int idx = 2; + + cHeader[0] = (((uint8_t)(eOp&0x0f)))|0x80; + + uint64_t iLen = sData.getSize(); + if( iLen < 126 ) + { + cHeader[1] = ((uint8_t)iLen); + } + else if( iLen < 2147483648 ) + { + cHeader[1] = ((uint8_t)126); + uint16_t uLen = iLen; + uLen = htobe16( uLen ); + memcpy( cHeader+idx, &uLen, 2 ); + idx += 2; + } + else + { + cHeader[1] = ((uint8_t)127); + uint64_t iTmp = htobe64( iLen ); + memcpy( cHeader+idx, &iTmp, 8 ); + idx += 8; + } +/* + Bu::println("Message size: %1 (%2)").arg( iLen ).arg( iLen, Bu::Fmt::bin(4) ); + for( int j = 0; j < idx; j++ ) + { + Bu::print(" %1").arg( cHeader[j], Bu::Fmt::bin(8) ); + } + Bu::println(""); +*/ + pClient->write( cHeader, idx ); + pClient->write( sData ); +} + +bool Bu::ProtocolWebSocket::stateProtoId() +{ + Bu::String sLine; + if( !readHttpHdrLine( sLine ) ) + return false; + + Bu::StringList lChunks = sLine.split(' '); + if( lChunks.getSize() != 3 ) + { + pClient->disconnect(); + return false; + } + Bu::StringList::iterator i = lChunks.begin(); + if( *i != "GET" ) + { + pClient->disconnect(); + return false; + } + sPath = *(++i); + if( *(++i) != "HTTP/1.1" ) + { + pClient->disconnect(); + return false; + } + + eStatus = stHandshake; + + return true; +} + +bool Bu::ProtocolWebSocket::stateHandshake() +{ + Bu::String sLine; + if( !readHttpHdrLine( sLine ) ) + return false; + + if( sLine.getSize() == 0 ) + { + if( !processHeaders() ) + return false; + onHandshakeComplete(); + eStatus = stReady; + return true; + } + + int iPos = sLine.findIdx(':'); + if( iPos < 0 ) + { + pClient->disconnect(); + return false; + } + Bu::String sKey( sLine, iPos ); + Bu::String sValue( sLine.getSubStrIdx( iPos+2 ) ); + + if( !hHeader.has( sKey ) ) + { + hHeader.insert( sKey, Bu::StringList() ); + } + hHeader.get( sKey ).append( sValue ); + +// Bu::println("Hdr: >>%1<<").arg( sLine ); +// Bu::println("%1 = %2").arg( sKey ).arg( sValue ); + + return true; +} + +bool Bu::ProtocolWebSocket::readHttpHdrLine( Bu::String &sLine ) +{ + char buf[1024]; + int iSize = pClient->peek( buf, 1024 ); + for( int j = 0; j < iSize-1; j++ ) + { + if( buf[j] == '\r' && buf[j+1] == '\n' ) + { + pClient->seek(j+2); + sLine.set( buf, j ); + return true; + } + } + return false; +} + +bool Bu::ProtocolWebSocket::processHeaders() +{ + if( !headerMatch("Connection", "Upgrade") || + !headerMatch("Upgrade", "websocket") || + !headerMatch("Sec-WebSocket-Version", "13") ) + { + pClient->disconnect(); + return false; + } + + Bu::String sNonce; + if( !hHeader.has("Sec-WebSocket-Key") ) + { + pClient->disconnect(); + return false; + } + + sNonce = hHeader.get("Sec-WebSocket-Key").first(); + + Bu::String sGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + Bu::Sha1 sum; + sum.addData( sNonce+sGuid ); + Bu::MemBuf mbOut; + Bu::Base64 bOut( mbOut ); + sum.writeResult( bOut ); + bOut.stop(); + + Bu::println("accept: %1").arg( mbOut.getString() ); + + pClient->write("HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: " + mbOut.getString() + "\r\n" + "\r\n" + ); + + return true; +} + +bool Bu::ProtocolWebSocket::headerMatch( const Bu::String &sKey, const Bu::String &sValue ) +{ + if( !hHeader.has( sKey ) ) + return false; + + for( Bu::StringList::iterator i = hHeader.get( sKey ).begin(); i; i++ ) + { + if( *i == sValue ) + return true; + } + + return false; +} + +bool Bu::ProtocolWebSocket::parseMessage() +{ + if( pClient->getInputSize() < 2 ) + return false; + + uint8_t buf[32]; + int64_t iTgtLength = 2; + pClient->peek( buf, 2, 0 ); + Operation eOp = (Operation)(buf[0]&0x0f); + int64_t iLen = buf[1]&(~0x80); + bool bMasked = (buf[1]&0x80) == 0x80; + if( iLen == 126 ) + { + uint16_t iLenBuf; + if( pClient->getInputSize() < iTgtLength+2 ) + return false; + pClient->peek( &iLenBuf, 2, iTgtLength ); + iTgtLength += 2; + iLen = be16toh( iLenBuf ); + } + else if( iLen == 127 ) + { + if( pClient->getInputSize() < iTgtLength+8 ) + return false; + pClient->peek( &iLen, 8, iTgtLength ); + iTgtLength += 8; + iLen = be64toh( iLen ); + } + char cMask[4]; + if( bMasked ) + { + if( pClient->getInputSize() < iTgtLength+4 ) + return false; + pClient->peek( cMask, 4, iTgtLength ); + iTgtLength += 4; + } + if( pClient->getInputSize() < iTgtLength+iLen ) + return false; + + pClient->seek( iTgtLength ); + + Bu::String sData( iLen ); + int iRead = 0; + do + { + iRead += pClient->read( sData.getStr()+iRead, iLen-iRead ); + } while( iRead < iLen ); + if( bMasked ) + { + for( int j = 0; j < iLen; j++ ) + { + sData[j] = sData[j]^cMask[j%4]; + } + } + + Bu::println(""); + Bu::println("Data: >>%1<<").arg( sData ); + + onNewMessage( sData, eOp ); + + return true; +} + diff --git a/src/unstable/protocolwebsocket.h b/src/unstable/protocolwebsocket.h new file mode 100644 index 0000000..cf00d34 --- /dev/null +++ b/src/unstable/protocolwebsocket.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2007-2014 Xagasoft, All rights reserved. + * + * This file is part of the libbu++ library and is released under the + * terms of the license contained in the file LICENSE. + */ + +#ifndef BU_PROTOCOL_WEB_SOCKET_H +#define BU_PROTOCOL_WEB_SOCKET_H + +#include "bu/protocol.h" +#include "bu/hash.h" + +namespace Bu +{ + class ProtocolWebSocket : public Bu::Protocol + { + public: + enum Operation + { + Continuation = 0, + Text = 1, + Binary = 2, + ConClose = 8, + Ping = 9, + Pong = 10 + }; + + public: + ProtocolWebSocket(); + virtual ~ProtocolWebSocket(); + + virtual void onNewConnection( Bu::Client *pClient ); + virtual void onNewData( Bu::Client *pClient ); + + virtual void onHandshakeComplete()=0; + virtual void onNewMessage( const Bu::String &sData, Operation eOp )=0; + void writeMessage( const Bu::String &sData, Operation eOp=Text ); + + private: + bool stateProtoId(); + bool stateHandshake(); + bool processHeaders(); + bool headerMatch( const Bu::String &sKey, const Bu::String &sValue ); + bool readHttpHdrLine( Bu::String &sLine ); + bool parseMessage(); + + private: + enum Status + { + stProtoId = 0, + stHandshake = 1, + stReady = 10 + }; + + protected: + Bu::Client *pClient; + Status eStatus; + Bu::String sPath; + Bu::Hash hHeader; + }; +} + +#endif -- cgit v1.2.3