/** * @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 #include #include #include #include #include #include #include #include #include #include #include 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; }