1166 lines
31 KiB
C++
1166 lines
31 KiB
C++
/**
|
|
* @file kWebSocket.cpp
|
|
*
|
|
* @internal
|
|
* Copyright (C) 2017-2021 by LMI Technologies Inc.
|
|
* Licensed under the MIT License.
|
|
* Redistributed files must retain the above copyright notice.
|
|
*/
|
|
|
|
#include <kApi/Io/kWebSocket.h>
|
|
|
|
#include <kApi/Crypto/kSha1Hash.h>
|
|
#include <kApi/Data/kArray1.h>
|
|
#include <kApi/Data/kString.h>
|
|
#include <kApi/Io/kHttpServerChannel.h>
|
|
#include <kApi/Io/kHttpServerRequest.h>
|
|
#include <kApi/Io/kHttpServerResponse.h>
|
|
#include <kApi/Io/kMemory.h>
|
|
#include <kApi/Io/kSerializer.h>
|
|
#include <kApi/Threads/kLock.h>
|
|
#include <kApi/Threads/kPeriodic.h>
|
|
#include <kApi/Threads/kTimer.h>
|
|
|
|
kBeginClassEx(k, kWebSocket)
|
|
kAddPrivateVMethod(kWebSocket, kObject, VRelease)
|
|
kAddPrivateVMethod(kWebSocket, kStream, VReadSomeImpl)
|
|
kAddPrivateVMethod(kWebSocket, kStream, VWriteImpl)
|
|
kAddPrivateVMethod(kWebSocket, kStream, VFill)
|
|
|
|
kEndClassEx()
|
|
|
|
kBeginEnumEx(k, kWebSocketDataType)
|
|
kAddEnumerator(kWebSocketDataType, kWEB_SOCKET_DATA_TYPE_NULL)
|
|
kAddEnumerator(kWebSocketDataType, kWEB_SOCKET_DATA_TYPE_UTF8)
|
|
kAddEnumerator(kWebSocketDataType, kWEB_SOCKET_DATA_TYPE_BINARY)
|
|
kEndEnumEx()
|
|
|
|
kFx(kStatus) kWebSocket_Construct(kWebSocket* webSocket, kAlloc allocator)
|
|
{
|
|
kAlloc alloc = kAlloc_Fallback(allocator);
|
|
kType type = kTypeOf(kWebSocket);
|
|
kStatus status;
|
|
|
|
kCheck(kAlloc_GetObject(alloc, type, webSocket));
|
|
|
|
if (!kSuccess(status = xkWebSocket_Init(*webSocket, type, kNULL, alloc)))
|
|
{
|
|
kAlloc_FreeRef(alloc, webSocket);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_ConstructFromRequest(kWebSocket* webSocket, kHttpServerChannel channel, kAlloc allocator)
|
|
{
|
|
kAlloc alloc = kAlloc_Fallback(allocator);
|
|
kType type = kTypeOf(kWebSocket);
|
|
kStatus status;
|
|
|
|
kCheck(kAlloc_GetObject(alloc, type, webSocket));
|
|
|
|
if (!kSuccess(status = xkWebSocket_Init(*webSocket, type, channel, alloc)))
|
|
{
|
|
kAlloc_FreeRef(alloc, webSocket);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_Init(kWebSocket webSocket, kType type, kHttpServerChannel channel, kAlloc allocator)
|
|
{
|
|
kObjR(kWebSocket, webSocket);
|
|
kStatus status;
|
|
|
|
kCheck(kStream_Init(webSocket, type, allocator));
|
|
|
|
obj->isClient = kIsNull(channel);
|
|
obj->shouldMaskSend = obj->isClient;
|
|
obj->state = 0;
|
|
obj->lock = kNULL;
|
|
obj->tcpClient = kNULL;
|
|
obj->serializer = kNULL;
|
|
obj->maskBuffer = kNULL;
|
|
obj->sendInfo.sendType = kWEB_SOCKET_DATA_TYPE_BINARY;
|
|
obj->sendInfo.inMessage = kFALSE;
|
|
kZero(obj->recvInfo);
|
|
obj->lastPongTime = 0;
|
|
obj->backgroundTimer = kNULL;
|
|
|
|
kTry
|
|
{
|
|
kTest(kLock_Construct(&obj->lock, allocator));
|
|
|
|
if (kIsNull(channel))
|
|
{
|
|
kTest(kTcpClient_Construct(&obj->tcpClient, kIP_VERSION_4, allocator));
|
|
}
|
|
else
|
|
{
|
|
kTest(xkWebSocket_ProcessHttpUpgradeRequest(webSocket, channel));
|
|
kTest(kHttpServerChannel_DetachClient(channel, &obj->tcpClient));
|
|
|
|
obj->state = xkWEB_SOCKET_STATE_CONNECTED;
|
|
}
|
|
|
|
//configure client buffers to support efficient frame header parsing/formatting
|
|
kTest(kTcpClient_SetWriteBuffers(obj->tcpClient, -1, xkWEB_SOCKET_TCP_CLIENT_WRITE_BUFFER));
|
|
kTest(kTcpClient_SetReadBuffers(obj->tcpClient, -1, xkWEB_SOCKET_TCP_CLIENT_READ_BUFFER));
|
|
|
|
if (obj->shouldMaskSend)
|
|
{
|
|
kTest(kArray1_Construct(&obj->maskBuffer, kTypeOf(kByte), xkWEB_SOCKET_MASK_BUFFER_SIZE, allocator));
|
|
}
|
|
|
|
kTest(kSerializer_Construct(&obj->serializer, obj->tcpClient, kNULL, allocator));
|
|
kTest(kSerializer_SetEndianness(obj->serializer, kENDIANNESS_BIG));
|
|
|
|
kTest(kPeriodic_Construct(&obj->backgroundTimer, allocator));
|
|
kTest(kPeriodic_Start(obj->backgroundTimer, xkWEB_SOCKET_BACKGROUND_TIMER_PERIOD, xkWebSocket_OnBackgroundTimer, webSocket));
|
|
}
|
|
kCatch(&status)
|
|
{
|
|
xkWebSocket_VRelease(webSocket);
|
|
|
|
kEndCatch(status);
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_VRelease(kWebSocket webSocket)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
|
|
kCheck(kDestroyRef(&obj->backgroundTimer));
|
|
|
|
kCheck(kDestroyRef(&obj->serializer));
|
|
kCheck(kDestroyRef(&obj->tcpClient));
|
|
|
|
kCheck(kDestroyRef(&obj->lock));
|
|
|
|
kCheck(kDestroyRef(&obj->maskBuffer));
|
|
|
|
kCheck(kObject_FreeMem(webSocket, obj->base.readBuffer));
|
|
kCheck(kObject_FreeMem(webSocket, obj->base.writeBuffer));
|
|
|
|
kCheck(kStream_VRelease(webSocket));
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kWebSocket_SetWriteBuffers(kWebSocket webSocket, kSSize socketSize, kSSize clientSize)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
|
|
kLock_Enter(obj->lock);
|
|
|
|
kTry
|
|
{
|
|
kTestState(obj->base.writeBegin == 0);
|
|
|
|
if (socketSize >= 0)
|
|
{
|
|
kTest(kTcpClient_SetWriteBuffers(obj->tcpClient, socketSize, -1));
|
|
}
|
|
|
|
if (clientSize >= 0)
|
|
{
|
|
kTest(kObject_FreeMemRef(webSocket, &obj->base.writeBuffer));
|
|
|
|
obj->base.writeCapacity = 0;
|
|
obj->base.writeBegin = 0;
|
|
obj->base.writeEnd = 0;
|
|
|
|
if (clientSize > 0)
|
|
{
|
|
kTest(kObject_GetMem(webSocket, (kSize)clientSize, &obj->base.writeBuffer));
|
|
obj->base.writeCapacity = (kSize)clientSize;
|
|
obj->base.writeEnd = (kSize)clientSize;
|
|
}
|
|
}
|
|
}
|
|
kFinally
|
|
{
|
|
kLock_Exit(obj->lock);
|
|
|
|
kEndFinally();
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kWebSocket_SetReadBuffers(kWebSocket webSocket, kSSize socketSize, kSSize clientSize)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
|
|
kLock_Enter(obj->lock);
|
|
|
|
kTry
|
|
{
|
|
kTestState((obj->base.readEnd - obj->base.readBegin) == 0);
|
|
|
|
if (socketSize >= 0)
|
|
{
|
|
kTest(kTcpClient_SetReadBuffers(obj->tcpClient, socketSize, -1));
|
|
}
|
|
|
|
if (clientSize >= 0)
|
|
{
|
|
kTest(kObject_FreeMemRef(webSocket, &obj->base.readBuffer));
|
|
|
|
obj->base.readCapacity = 0;
|
|
obj->base.readBegin = 0;
|
|
obj->base.readEnd = 0;
|
|
|
|
if (clientSize > 0)
|
|
{
|
|
kTest(kObject_GetMem(webSocket, (kSize)clientSize, &obj->base.readBuffer));
|
|
obj->base.readCapacity = (kSize)clientSize;
|
|
}
|
|
}
|
|
}
|
|
kFinally
|
|
{
|
|
kLock_Exit(obj->lock);
|
|
|
|
kEndFinally();
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kWebSocket_Connect(kWebSocket webSocket, kIpAddress address, k32u port, const kChar* host, const kChar* uri, k64u timeout)
|
|
{
|
|
kObjR(kWebSocket, webSocket);
|
|
kByte key[16] = { 0 };
|
|
kStatus status;
|
|
|
|
kLock_Enter(obj->lock);
|
|
|
|
kTry
|
|
{
|
|
kTestState(obj->state == xkWEB_SOCKET_STATE_INITIALIZED);
|
|
|
|
kTest(kTcpClient_Connect(obj->tcpClient, address, port, timeout));
|
|
|
|
obj->state = xkWEB_SOCKET_STATE_CONNECTING;
|
|
|
|
kTest(kRandomBytes(key, sizeof(key)));
|
|
|
|
kTest(xkWebSocket_SendHttpUpgradeRequest(webSocket, host, port, uri, key, sizeof(key)));
|
|
kTest(xkWebSocket_ReceiveHttpUpgradeResponse(webSocket, key, sizeof(key)));
|
|
|
|
obj->state = xkWEB_SOCKET_STATE_CONNECTED;
|
|
}
|
|
kCatchEx(&status)
|
|
{
|
|
if (obj->state == xkWEB_SOCKET_STATE_CONNECTING)
|
|
{
|
|
kTcpClient_Shutdown(obj->tcpClient);
|
|
}
|
|
|
|
obj->state = xkWEB_SOCKET_STATE_CLOSED;
|
|
|
|
kEndCatchEx(status);
|
|
}
|
|
kFinallyEx
|
|
{
|
|
kLock_Exit(obj->lock);
|
|
|
|
kEndFinallyEx();
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kWebSocket_Close(kWebSocket webSocket)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
kStatus status;
|
|
|
|
kLock_Enter(obj->lock);
|
|
|
|
kTry
|
|
{
|
|
if (xkWebSocket_IsValid(webSocket))
|
|
{
|
|
kTest(xkWebSocket_WriteFrame(webSocket, kNULL, 0, xkWEB_SOCKET_OP_CODE_CLOSE, kTRUE));
|
|
|
|
obj->state = xkWEB_SOCKET_STATE_CLOSING;
|
|
|
|
//discard frames until close response received (close response generates error)
|
|
while (kSuccess(kWebSocket_Receive(webSocket, xkWEB_SOCKET_CLOSE_TIMEOUT)));
|
|
}
|
|
}
|
|
kCatchEx(&status)
|
|
{
|
|
kEndCatchEx(kOK);
|
|
}
|
|
kFinallyEx
|
|
{
|
|
obj->state = xkWEB_SOCKET_STATE_CLOSED;
|
|
|
|
kTcpClient_Shutdown(obj->tcpClient);
|
|
|
|
kLock_Exit(obj->lock);
|
|
|
|
kEndFinallyEx();
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kWebSocket_SetSendType(kWebSocket webSocket, kWebSocketDataType type)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
|
|
kLock_Enter(obj->lock);
|
|
|
|
kTry
|
|
{
|
|
kTestState(!obj->sendInfo.inMessage);
|
|
|
|
obj->sendInfo.sendType = type;
|
|
}
|
|
kFinally
|
|
{
|
|
kLock_Exit(obj->lock);
|
|
|
|
kEndFinally();
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kWebSocket_Send(kWebSocket webSocket)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
|
|
kLock_Enter(obj->lock);
|
|
|
|
kTry
|
|
{
|
|
kTestState(xkWebSocket_IsValid(webSocket));
|
|
|
|
kTest(xkWebSocket_WriteDataFrame(webSocket, obj->base.writeBuffer, obj->base.writeBegin, kTRUE));
|
|
|
|
obj->base.writeBegin = 0;
|
|
}
|
|
kFinally
|
|
{
|
|
kLock_Exit(obj->lock);
|
|
|
|
kEndFinally();
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kWebSocket_Receive(kWebSocket webSocket, k64u timeout)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
|
|
kLock_Enter(obj->lock);
|
|
|
|
kTry
|
|
{
|
|
kTest(xkWebSocket_IsValid(webSocket));
|
|
|
|
//discard any partially read-out messages
|
|
kTest(xkWebSocket_DiscardReceive(webSocket));
|
|
|
|
//wait for the next data message header to arrive
|
|
kTest(xkWebSocket_BeginReceive(webSocket, timeout));
|
|
|
|
obj->recvInfo.inMessage = kTRUE;
|
|
|
|
//latch receive data type, to prevent being overwritten
|
|
obj->recvInfo.lastRecvType = obj->recvInfo.recvType;
|
|
}
|
|
kFinally
|
|
{
|
|
kLock_Exit(obj->lock);
|
|
|
|
kEndFinally();
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kWebSocket_WriteMessage(kWebSocket webSocket, const void* buffer, kSize size)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
|
|
kLock_Enter(obj->lock);
|
|
|
|
kTry
|
|
{
|
|
kTestState(xkWebSocket_IsValid(webSocket));
|
|
|
|
kTestState(obj->base.writeBegin == 0);
|
|
kTestState(!obj->sendInfo.inMessage);
|
|
|
|
kTest(xkWebSocket_WriteDataFrame(webSocket, (const kByte*) buffer, size, kTRUE));
|
|
}
|
|
kFinally
|
|
{
|
|
kLock_Exit(obj->lock);
|
|
|
|
kEndFinally();
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kWebSocket_ReadMessage(kWebSocket webSocket, kMemory stream, k64u timeout)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
kStatus readStatus = kOK;
|
|
|
|
kLock_Enter(obj->lock);
|
|
|
|
kTry
|
|
{
|
|
kTestState(xkWebSocket_IsValid(webSocket));
|
|
|
|
kTest(kWebSocket_Receive(webSocket, timeout));
|
|
|
|
while (readStatus != kERROR_NOT_FOUND)
|
|
{
|
|
kSize bufferPosition = (kSize)kMemory_Position(stream);
|
|
kSize bytesRead = 0;
|
|
|
|
if (kMemory_Capacity(stream) == bufferPosition)
|
|
{
|
|
kTest(kMemory_Reserve(stream, kMax_(256, 2 * kMemory_Capacity(stream))));
|
|
}
|
|
|
|
readStatus = kStream_ReadSome(webSocket, kMemory_At(stream, bufferPosition), 1, kMemory_Capacity(stream) - bufferPosition, &bytesRead);
|
|
|
|
if (readStatus != kERROR_NOT_FOUND)
|
|
{
|
|
kTest(readStatus);
|
|
}
|
|
|
|
kTest(kMemory_SetLength(stream, bufferPosition + bytesRead));
|
|
kTest(kStream_Seek(stream, 0, kSEEK_ORIGIN_END));
|
|
}
|
|
}
|
|
kFinally
|
|
{
|
|
kLock_Exit(obj->lock);
|
|
|
|
kEndFinally();
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kWebSocket_Cancel(kWebSocket webSocket)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
|
|
//kTcpClient_Cancel is thread-safe; no need to lock
|
|
return kTcpClient_Cancel(obj->tcpClient);
|
|
}
|
|
|
|
kFx(kStatus) kWebSocket_SendPing(kWebSocket webSocket)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
|
|
kLock_Enter(obj->lock);
|
|
|
|
kTry
|
|
{
|
|
kTestState(xkWebSocket_IsValid(webSocket));
|
|
|
|
kTest(xkWebSocket_WriteFrame(webSocket, kNULL, 0, xkWEB_SOCKET_OP_CODE_PING, kTRUE));
|
|
}
|
|
kFinally
|
|
{
|
|
kLock_Exit(obj->lock);
|
|
|
|
kEndFinally();
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(k64u) kWebSocket_LastPong(kWebSocket webSocket)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
k64u lastPong;
|
|
|
|
kLock_Enter(obj->lock);
|
|
{
|
|
lastPong = obj->lastPongTime;
|
|
}
|
|
kLock_Exit(obj->lock);
|
|
|
|
return lastPong;
|
|
}
|
|
|
|
kFx(kWebSocketDataType) kWebSocket_SendType(kWebSocket webSocket)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
kWebSocketDataType sendType;
|
|
|
|
kLock_Enter(obj->lock);
|
|
{
|
|
sendType = obj->sendInfo.sendType;
|
|
}
|
|
kLock_Exit(obj->lock);
|
|
|
|
return sendType;
|
|
}
|
|
|
|
kFx(kWebSocketDataType) kWebSocket_ReceiveType(kWebSocket webSocket)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
kWebSocketDataType recvType;
|
|
|
|
kLock_Enter(obj->lock);
|
|
{
|
|
recvType = obj->recvInfo.lastRecvType;
|
|
}
|
|
kLock_Exit(obj->lock);
|
|
|
|
return recvType;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_VReadSomeImpl(kWebSocket webSocket, void* buffer, kSize minCount, kSize maxCount, kSize* bytesRead)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
kSize readCount = 0;
|
|
|
|
kLock_Enter(obj->lock);
|
|
|
|
kTry
|
|
{
|
|
kByte* dest = (kByte*) buffer;
|
|
kSize copyCount, mediumCount;
|
|
|
|
kTestState(xkWebSocket_IsValid(webSocket));
|
|
kTestArgs(minCount <= maxCount);
|
|
|
|
//consume any bytes in the read buffer first
|
|
if ((obj->base.readEnd - obj->base.readBegin) > 0)
|
|
{
|
|
copyCount = kMin_(maxCount, obj->base.readEnd - obj->base.readBegin);
|
|
|
|
kMemCopy(dest, &obj->base.readBuffer[obj->base.readBegin], copyCount);
|
|
|
|
obj->base.readBegin += copyCount;
|
|
readCount += copyCount;
|
|
}
|
|
|
|
//if the request is not yet satisfied
|
|
if (readCount < minCount)
|
|
{
|
|
//if the request is larger than the internal read buffer, read directly into the caller's buffer; else read into the internal buffer
|
|
if ((maxCount - readCount) > obj->base.readCapacity)
|
|
{
|
|
kTest(xkWebSocket_ReadAtLeast(webSocket, &dest[readCount], minCount - readCount, maxCount - readCount, &mediumCount));
|
|
|
|
obj->base.readBegin = obj->base.readEnd = 0;
|
|
readCount += mediumCount;
|
|
}
|
|
else
|
|
{
|
|
obj->base.readBegin = obj->base.readEnd = 0;
|
|
|
|
kTest(xkWebSocket_ReadAtLeast(webSocket, &obj->base.readBuffer[obj->base.readEnd], minCount - readCount, obj->base.readCapacity - obj->base.readEnd, &mediumCount));
|
|
|
|
copyCount = kMin_(mediumCount, maxCount - readCount);
|
|
|
|
kTest(kMemCopy(&dest[readCount], &obj->base.readBuffer[obj->base.readEnd], copyCount));
|
|
|
|
obj->base.readBegin += copyCount;
|
|
obj->base.readEnd += mediumCount;
|
|
readCount += copyCount;
|
|
}
|
|
}
|
|
}
|
|
kFinally
|
|
{
|
|
if (!kIsNull(bytesRead))
|
|
{
|
|
*bytesRead = readCount;
|
|
}
|
|
|
|
kLock_Exit(obj->lock);
|
|
|
|
kEndFinally();
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_VWriteImpl(kWebSocket webSocket, const void* buffer, kSize size)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
|
|
kLock_Enter(obj->lock);
|
|
|
|
kTry
|
|
{
|
|
kTestState(xkWebSocket_IsValid(webSocket));
|
|
|
|
//if the internal write buffer already has content, but not enough free space, flush frame
|
|
if ((obj->base.writeBegin > 0) && ((obj->base.writeEnd - obj->base.writeBegin) < size))
|
|
{
|
|
kTest(xkWebSocket_WriteDataFrame(webSocket, obj->base.writeBuffer, obj->base.writeBegin, kFALSE));
|
|
obj->base.writeBegin = 0;
|
|
}
|
|
|
|
//if the write is larger than the internal write buffer, write directly to the medium; else write to the internal buffer
|
|
if ((obj->base.writeEnd - obj->base.writeBegin) < size)
|
|
{
|
|
kTest(xkWebSocket_WriteDataFrame(webSocket, (const kByte*) buffer, size, kFALSE));
|
|
}
|
|
else
|
|
{
|
|
kTest(kMemCopy(&obj->base.writeBuffer[obj->base.writeBegin], buffer, size));
|
|
obj->base.writeBegin += size;
|
|
}
|
|
}
|
|
kFinally
|
|
{
|
|
kLock_Exit(obj->lock);
|
|
|
|
kEndFinally();
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_VFill(kWebSocket webSocket)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
kSize mediumCount;
|
|
|
|
kLock_Enter(obj->lock);
|
|
|
|
kTry
|
|
{
|
|
kTestState(xkWebSocket_IsValid(webSocket));
|
|
|
|
kTest(xkWebSocket_ReadAtLeast(webSocket, &obj->base.readBuffer[0], 1, obj->base.readCapacity, &mediumCount));
|
|
|
|
obj->base.readBegin = 0;
|
|
obj->base.readEnd = mediumCount;
|
|
}
|
|
kFinally
|
|
{
|
|
kLock_Exit(obj->lock);
|
|
|
|
kEndFinally();
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_SendHttpUpgradeRequest(kWebSocket webSocket, const kChar* host, k32u port, const kChar* uri, const kByte* key, kSize keyLength)
|
|
{
|
|
kObjR(kWebSocket, webSocket);
|
|
kString header = kNULL;
|
|
kString keyString = kNULL;
|
|
|
|
kTry
|
|
{
|
|
kTest(kString_Construct(&keyString, kNULL, kObject_Alloc(webSocket)));
|
|
kTest(kString_Construct(&header, kNULL, kObject_Alloc(webSocket)));
|
|
|
|
kTest(kBase64Encode(key, keyLength, keyString));
|
|
|
|
kTest(kString_Setf(header, "GET %s HTTP/1.1\r\n", uri));
|
|
kTest(kString_Addf(header, "HOST: %s:%u\r\n", host, port));
|
|
kTest(kString_Add(header, "Upgrade: websocket\r\n"));
|
|
kTest(kString_Add(header, "Connection: Upgrade\r\n"));
|
|
kTest(kString_Addf(header, "Sec-WebSocket-Key: %s\r\n", kString_Chars(keyString)));
|
|
kTest(kString_Add(header, "Sec-WebSocket-Version: 13\r\n\r\n"));
|
|
|
|
kTest(kStream_Write(obj->tcpClient, kString_Chars(header), kString_Length(header)));
|
|
kTest(kStream_Flush(obj->tcpClient));
|
|
}
|
|
kFinally
|
|
{
|
|
kObject_Destroy(header);
|
|
kObject_Destroy(keyString);
|
|
|
|
kEndFinally();
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_ReceiveHttpUpgradeResponse(kWebSocket webSocket, const kByte* key, kSize keyLength)
|
|
{
|
|
kObjR(kWebSocket, webSocket);
|
|
const kChar endOfResponse[] = { '\r', '\n', '\r', '\n' };
|
|
kSize responsePos = 0;
|
|
kSize bytesRead = 0;
|
|
|
|
//TODO: we currently discard the response; should parse and verify its contents
|
|
do
|
|
{
|
|
kChar nextByte;
|
|
|
|
//TODO: restructure to use kTcpClient_Wait, with timeout?
|
|
kCheck(kStream_Read(obj->tcpClient, &nextByte, 1));
|
|
|
|
if (nextByte == endOfResponse[responsePos])
|
|
{
|
|
responsePos++;
|
|
}
|
|
else
|
|
{
|
|
responsePos = (nextByte == endOfResponse[0]) ? 1u : 0u;
|
|
}
|
|
}
|
|
while ((++bytesRead < xkWEB_SOCKET_MAX_HTTP_RESPONSE_LENGTH) && (responsePos < sizeof(endOfResponse)));
|
|
|
|
return (responsePos == sizeof(endOfResponse)) ? kOK : kERROR_STREAM;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_ProcessHttpUpgradeRequest(kWebSocket webSocket, kHttpServerChannel channel)
|
|
{
|
|
kObjR(kWebSocket, webSocket);
|
|
kHttpServerRequest request = kHttpServerChannel_Request(channel);
|
|
kHttpServerRequest response = kHttpServerChannel_Response(channel);
|
|
const kChar* key = kNULL;
|
|
kString acceptKey = kNULL;
|
|
kHash hash = kNULL;
|
|
kByte* digest = kNULL;
|
|
|
|
kTry
|
|
{
|
|
kTestState(!kIsNull(key = kHttpServerRequest_FindHeaderValue(request, "Sec-WebSocket-Key")));
|
|
|
|
kTest(kSha1Hash_Construct(&hash, kObject_Alloc(webSocket)));
|
|
kTest(kObject_GetMem(webSocket, kHash_DigestSize(hash), &digest));
|
|
|
|
kTest(kString_Construct(&acceptKey, key, kObject_Alloc(webSocket)));
|
|
kTest(kString_Add(acceptKey, xkWEB_SOCKET_HANDSHAKE_GUID));
|
|
|
|
kTest(kHash_Update(hash, kString_Chars(acceptKey), kString_Length(acceptKey)));
|
|
kTest(kHash_Digest(hash, digest, kHash_DigestSize(hash)));
|
|
|
|
kTest(kBase64Encode(digest, kHash_DigestSize(hash), acceptKey));
|
|
|
|
kTest(kHttpServerResponse_SetStatus(response, kHTTP_STATUS_SWITCHING_PROTOCOLS));
|
|
kTest(kHttpServerResponse_SetHeader(response, "Upgrade", "websocket"));
|
|
kTest(kHttpServerResponse_SetHeader(response, "Connection", "Upgrade"));
|
|
kTest(kHttpServerResponse_SetHeader(response, "Sec-WebSocket-Accept", kString_Chars(acceptKey)));
|
|
}
|
|
kFinally
|
|
{
|
|
kObject_Destroy(acceptKey);
|
|
kObject_Destroy(hash);
|
|
kObject_FreeMem(webSocket, digest);
|
|
|
|
kEndFinally();
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_WriteDataFrame(kWebSocket webSocket, const kByte* buffer, kSize count, kBool finalFrame)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
k8u opCode;
|
|
|
|
if (obj->sendInfo.inMessage)
|
|
{
|
|
opCode = xkWEB_SOCKET_OP_CODE_CONTINUE;
|
|
}
|
|
else if (obj->sendInfo.sendType == kWEB_SOCKET_DATA_TYPE_UTF8)
|
|
{
|
|
opCode = xkWEB_SOCKET_OP_CODE_UTF8;
|
|
}
|
|
else
|
|
{
|
|
opCode = xkWEB_SOCKET_OP_CODE_BINARY;
|
|
}
|
|
|
|
kCheck(xkWebSocket_WriteFrame(webSocket, buffer, count, opCode, finalFrame));
|
|
|
|
obj->base.bytesWritten += count;
|
|
|
|
obj->sendInfo.inMessage = !finalFrame;
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_WriteFrame(kWebSocket webSocket, const kByte* buffer, kSize count, k8u opCode, kBool finalFrame)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
kByte maskKey[4];
|
|
|
|
kCheck(kRandomBytes(maskKey, sizeof(maskKey)));
|
|
|
|
kCheck(xkWebSocket_WriteHeader(webSocket, opCode, finalFrame, obj->shouldMaskSend, maskKey, sizeof(maskKey), count));
|
|
|
|
if (!obj->shouldMaskSend)
|
|
{
|
|
kCheck(kStream_Write(obj->tcpClient, buffer, count));
|
|
}
|
|
else
|
|
{
|
|
kSize sent = 0;
|
|
kSize maskBufferSize = kArray1_Length(obj->maskBuffer);
|
|
kByte* maskBuffer = kArray1_DataT(obj->maskBuffer, kByte);
|
|
|
|
while (sent != count)
|
|
{
|
|
kSize chunkSize = kMin_(count-sent, maskBufferSize);
|
|
|
|
kCheck(xkWebSocket_ApplyMask(webSocket, &buffer[sent], maskBuffer, chunkSize, maskKey, sizeof(maskKey), sent));
|
|
|
|
kCheck(kStream_Write(obj->tcpClient, maskBuffer, chunkSize));
|
|
|
|
sent += chunkSize;
|
|
}
|
|
}
|
|
|
|
kCheck(kStream_Flush(obj->tcpClient));
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_WriteHeader(kWebSocket webSocket, k8u opCode, kBool finalFrame, kBool shouldMask, const kByte* maskKey, kSize maskKeySize, kSize payloadSize)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
k8u payloadSizeCode;
|
|
|
|
kCheck(kSerializer_Write8u(obj->serializer, (k8u) (xkWebSocket_SetFin(finalFrame) | xkWebSocket_SetOpCode(opCode))));
|
|
|
|
if (payloadSize < 126) payloadSizeCode = (k8u) payloadSize;
|
|
else if (payloadSize <= k16U_MAX) payloadSizeCode = xkWEB_SOCKET_16_BIT_PAYLOAD;
|
|
else payloadSizeCode = xkWEB_SOCKET_64_BIT_PAYLOAD;
|
|
|
|
kCheck(kSerializer_Write8u(obj->serializer, (k8u) (xkWebSocket_SetMaskEnabled(shouldMask) | xkWebSocket_SetPayloadCode(payloadSizeCode))));
|
|
|
|
if (payloadSizeCode == xkWEB_SOCKET_16_BIT_PAYLOAD)
|
|
{
|
|
kCheck(kSerializer_Write16u(obj->serializer, (k16u) payloadSize));
|
|
}
|
|
else if (payloadSizeCode == xkWEB_SOCKET_64_BIT_PAYLOAD)
|
|
{
|
|
kCheck(kSerializer_Write64u(obj->serializer, (k64u) payloadSize));
|
|
}
|
|
|
|
if (shouldMask)
|
|
{
|
|
kCheck(kSerializer_WriteByteArray(obj->serializer, maskKey, maskKeySize));
|
|
}
|
|
|
|
kCheck(kSerializer_Flush(obj->serializer));
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_DiscardReceive(kWebSocket webSocket)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
kByte discard[256];
|
|
kSize bytesRead;
|
|
|
|
//discard buffered bytes
|
|
obj->base.readBegin = obj->base.readEnd = 0;
|
|
|
|
//discard partially-unread-message bytes
|
|
if (obj->recvInfo.inMessage)
|
|
{
|
|
while (kSuccess(xkWebSocket_ReadAtLeast(webSocket, discard, 1, sizeof(discard), &bytesRead)));
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_BeginReceive(kWebSocket webSocket, k64u timeout)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
|
|
while (!obj->recvInfo.inDataFrame && kSuccess(kTcpClient_Wait(obj->tcpClient, timeout)))
|
|
{
|
|
kCheck(xkWebSocket_ProcessHeader(webSocket));
|
|
}
|
|
|
|
return (obj->recvInfo.inDataFrame) ? kOK : kERROR_TIMEOUT;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_ProcessHeader(kWebSocket webSocket)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
kStatus exception;
|
|
|
|
kTry
|
|
{
|
|
kTest(xkWebSocket_ParseHeader(webSocket));
|
|
kTest(xkWebSocket_InterpretHeader(webSocket));
|
|
|
|
if (!obj->recvInfo.inDataFrame)
|
|
{
|
|
kTest(xkWebSocket_ProcessControlFrame(webSocket));
|
|
}
|
|
}
|
|
kCatch(&exception)
|
|
{
|
|
if (exception == kERROR_CLOSED)
|
|
{
|
|
obj->state = xkWEB_SOCKET_STATE_CLOSED;
|
|
}
|
|
|
|
kEndCatch(exception);
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_ParseHeader(kWebSocket webSocket)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
k8u header0 = 0;
|
|
k8u header1 = 0;
|
|
k8u payloadCode = 0;
|
|
|
|
kCheck(kSerializer_Read8u(obj->serializer, &header0));
|
|
kCheck(kSerializer_Read8u(obj->serializer, &header1));
|
|
|
|
obj->recvInfo.opCode = xkWebSocket_OpCode(header0);
|
|
obj->recvInfo.isLastFrame = xkWebSocket_Fin(header0);
|
|
obj->recvInfo.isMasked = xkWebSocket_MaskEnabled(header1);
|
|
|
|
payloadCode = (k8u) xkWebSocket_PayloadCode(header1);
|
|
|
|
//if the payload size doesn't fit in the second header byte,
|
|
//the next 16 or 64 bits will contain the correct payload size
|
|
if (payloadCode < xkWEB_SOCKET_16_BIT_PAYLOAD)
|
|
{
|
|
obj->recvInfo.payloadSize = (kSize) payloadCode;
|
|
}
|
|
else if (payloadCode == xkWEB_SOCKET_16_BIT_PAYLOAD)
|
|
{
|
|
k16u payload;
|
|
|
|
kCheck(kSerializer_Read16u(obj->serializer, &payload));
|
|
|
|
obj->recvInfo.payloadSize = (kSize)payload;
|
|
}
|
|
else if (payloadCode == xkWEB_SOCKET_64_BIT_PAYLOAD)
|
|
{
|
|
k64u payload;
|
|
|
|
kCheck(kSerializer_Read64u(obj->serializer, &payload));
|
|
|
|
obj->recvInfo.payloadSize = (kSize)payload;
|
|
}
|
|
else
|
|
{
|
|
kCheck(kERROR_STREAM);
|
|
}
|
|
|
|
if (obj->recvInfo.isMasked)
|
|
{
|
|
kCheck(kSerializer_ReadByteArray(obj->serializer, &obj->recvInfo.maskKey[0], sizeof(obj->recvInfo.maskKey)));
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_InterpretHeader(kWebSocket webSocket)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
kBool isFirstDataFrame = (obj->recvInfo.opCode == xkWEB_SOCKET_OP_CODE_UTF8) || (obj->recvInfo.opCode == xkWEB_SOCKET_OP_CODE_BINARY);
|
|
kBool isNextDataFrame = (obj->recvInfo.opCode == xkWEB_SOCKET_OP_CODE_CONTINUE);
|
|
|
|
obj->recvInfo.isFirstDataFrame = isFirstDataFrame;
|
|
obj->recvInfo.inDataFrame = isFirstDataFrame || isNextDataFrame;
|
|
obj->recvInfo.dataRead = 0;
|
|
|
|
if (obj->recvInfo.opCode == xkWEB_SOCKET_OP_CODE_UTF8)
|
|
{
|
|
obj->recvInfo.recvType = kWEB_SOCKET_DATA_TYPE_UTF8;
|
|
}
|
|
else if (obj->recvInfo.opCode == xkWEB_SOCKET_OP_CODE_BINARY)
|
|
{
|
|
obj->recvInfo.recvType = kWEB_SOCKET_DATA_TYPE_BINARY;
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_ProcessControlFrame(kWebSocket webSocket)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
kByte payload[xkWEB_SOCKET_MAX_CONTROL_PAYLOAD];
|
|
kSize payloadSize = obj->recvInfo.payloadSize;
|
|
|
|
kCheckTrue(payloadSize <= xkWEB_SOCKET_MAX_CONTROL_PAYLOAD, kERROR_STREAM);
|
|
|
|
//control frame payload must be less than or equal to 125 bytes; control frames cannot be fragmented
|
|
kCheck(kStream_Read(obj->tcpClient, payload, payloadSize));
|
|
|
|
if (obj->recvInfo.isMasked)
|
|
{
|
|
kCheck(xkWebSocket_ApplyMask(webSocket, payload, payload, obj->recvInfo.payloadSize, obj->recvInfo.maskKey, sizeof(obj->recvInfo.maskKey), 0));
|
|
}
|
|
|
|
switch (obj->recvInfo.opCode)
|
|
{
|
|
case xkWEB_SOCKET_OP_CODE_CLOSE: kCheck(xkWebSocket_ProcessClose(webSocket, payload, payloadSize)); break;
|
|
case xkWEB_SOCKET_OP_CODE_PING: kCheck(xkWebSocket_ProcessPing(webSocket, payload, payloadSize)); break;
|
|
case xkWEB_SOCKET_OP_CODE_PONG: kCheck(xkWebSocket_ProcessPong(webSocket, payload, payloadSize)); break;
|
|
default: break;
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_ProcessPing(kWebSocket webSocket, const kByte* payload, kSize payloadSize)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
|
|
kCheck(xkWebSocket_WriteFrame(webSocket, payload, payloadSize, xkWEB_SOCKET_OP_CODE_PONG, kTRUE));
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_ProcessPong(kWebSocket webSocket, const kByte* payload, kSize payloadSize)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
|
|
obj->lastPongTime = kTimer_Now();
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_ProcessClose(kWebSocket webSocket, const kByte* payload, kSize payloadSize)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
|
|
obj->state = xkWEB_SOCKET_STATE_CLOSING;
|
|
|
|
xkWebSocket_WriteFrame(webSocket, payload, payloadSize, xkWEB_SOCKET_OP_CODE_CLOSE, kTRUE);
|
|
|
|
kTcpClient_Shutdown(obj->tcpClient);
|
|
|
|
obj->state = xkWEB_SOCKET_STATE_CLOSED;
|
|
|
|
return kERROR_CLOSED;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_ReadAtLeast(kWebSocket webSocket, kByte* buffer, kSize minCount, kSize maxCount, kSize* bytesRead)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
kSize totalBytesRead = 0;
|
|
kStatus exception;
|
|
|
|
kTry
|
|
{
|
|
while (totalBytesRead < minCount)
|
|
{
|
|
kSize payloadRemaining, minRequest, maxRequest;
|
|
kSize currentBytesRead = 0;
|
|
|
|
if (!obj->recvInfo.inMessage)
|
|
{
|
|
//generate special end-of-messsage error code
|
|
kThrow(kERROR_NOT_FOUND);
|
|
}
|
|
|
|
while (!obj->recvInfo.inDataFrame)
|
|
{
|
|
kTest(xkWebSocket_ProcessHeader(webSocket));
|
|
}
|
|
|
|
payloadRemaining = obj->recvInfo.payloadSize - obj->recvInfo.dataRead;
|
|
minRequest = kMin_(minCount, payloadRemaining);
|
|
maxRequest = kMin_(maxCount, payloadRemaining);
|
|
|
|
kTest(kStream_ReadSome(obj->tcpClient, &buffer[totalBytesRead], minRequest, maxRequest, ¤tBytesRead));
|
|
|
|
obj->base.bytesRead += currentBytesRead;
|
|
|
|
if (obj->recvInfo.isMasked)
|
|
{
|
|
kTest(xkWebSocket_ApplyMask(webSocket, &buffer[totalBytesRead], &buffer[totalBytesRead], currentBytesRead,
|
|
obj->recvInfo.maskKey, sizeof(obj->recvInfo.maskKey), obj->recvInfo.dataRead));
|
|
}
|
|
|
|
totalBytesRead += currentBytesRead;
|
|
obj->recvInfo.dataRead += currentBytesRead;
|
|
|
|
if ((obj->recvInfo.dataRead == obj->recvInfo.payloadSize))
|
|
{
|
|
obj->recvInfo.inDataFrame = kFALSE;
|
|
|
|
if (obj->recvInfo.isLastFrame)
|
|
{
|
|
obj->recvInfo.inMessage = kFALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
kCatchEx(&exception)
|
|
{
|
|
if (exception == kERROR_CLOSED)
|
|
{
|
|
obj->state = xkWEB_SOCKET_STATE_CLOSED;
|
|
}
|
|
|
|
kEndCatchEx(exception);
|
|
}
|
|
kFinallyEx
|
|
{
|
|
if (!kIsNull(bytesRead))
|
|
{
|
|
*bytesRead = totalBytesRead;
|
|
}
|
|
|
|
kEndFinallyEx();
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_ApplyMask(kWebSocket webSocket, const kByte* src, kByte* dest, kSize count, kByte* maskKey, kSize maskKeySize, kSize initialPosition)
|
|
{
|
|
kSize i;
|
|
|
|
kCheckArgs(maskKeySize == 4);
|
|
|
|
//frame masking algorithm, as per RFC 6455
|
|
for (i = 0; i < count; ++i)
|
|
{
|
|
dest[i] = (kByte)(src[i] ^ maskKey[(initialPosition + i) & 0x3]);
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kBool) xkWebSocket_IsValid(kWebSocket webSocket)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
|
|
return (obj->state == xkWEB_SOCKET_STATE_CONNECTED) && !kIsError(kTcpClient_Status(obj->tcpClient));
|
|
}
|
|
|
|
kFx(kStatus) xkWebSocket_OnBackgroundTimer(kWebSocket webSocket, kPeriodic timer)
|
|
{
|
|
kObj(kWebSocket, webSocket);
|
|
|
|
kLock_Enter(obj->lock);
|
|
{
|
|
if (xkWebSocket_IsValid(webSocket))
|
|
{
|
|
xkWebSocket_BeginReceive(webSocket, 0);
|
|
}
|
|
}
|
|
kLock_Exit(obj->lock);
|
|
|
|
return kOK;
|
|
}
|