/** * @file kTcpClient.cpp * * @internal * Copyright (C) 2008-2021 by LMI Technologies Inc. * Licensed under the MIT License. * Redistributed files must retain the above copyright notice. */ #define K_PLATFORM #include #include #include kBeginClassEx(k, kTcpClient) kAddPrivateVMethod(kTcpClient, kObject, VRelease) kAddPrivateVMethod(kTcpClient, kStream, VReadSomeImpl) kAddPrivateVMethod(kTcpClient, kStream, VWriteImpl) kAddPrivateVMethod(kTcpClient, kStream, VFlush) kAddPrivateVMethod(kTcpClient, kStream, VFill) kAddPrivateVMethod(kTcpClient, kStream, VSeek) kEndClassEx() kFx(kStatus) kTcpClient_Construct(kTcpClient* client, kIpVersion ipVersion, kAlloc allocator) { kAlloc alloc = kAlloc_Fallback(allocator); kStatus status; kCheck(kAlloc_GetObject(alloc, kTypeOf(kTcpClient), client)); if (!kSuccess(status = xkTcpClient_Init(*client, kTypeOf(kTcpClient), ipVersion, alloc))) { kAlloc_FreeRef(alloc, client); } return status; } kFx(kStatus) xkTcpClient_ConstructFromSocket(kTcpClient* client, kSocket socket, kAlloc allocator) { kAlloc alloc = kAlloc_Fallback(allocator); kType type = kTypeOf(kTcpClient); kStatus status; kCheck(kAlloc_GetObject(alloc, type, client)); if (!kSuccess(status = xkTcpClient_InitFromSocket(*client, type, socket, alloc))) { kAlloc_FreeRef(alloc, client); } return status; } kFx(kStatus) xkTcpClient_Init(kTcpClient client, kType type, kIpVersion ipVersion, kAlloc allocator) { kSocket socket = kNULL; kStatus status; kTry { kTest(kSocket_Construct(&socket, kIP_VERSION_4, kSOCKET_TYPE_TCP, allocator)); kTest(kSocket_Bind(socket, kIpAddress_Any(ipVersion), kIP_PORT_ANY)); kTest(xkTcpClient_InitFromSocket(client, type, socket, allocator)); } kCatch(&status) { kDestroyRef(&socket); kEndCatch(status); } return kOK; } kFx(kStatus) xkTcpClient_InitFromSocket(kTcpClient client, kType type, kSocket socket, kAlloc allocator) { kObjR(kTcpClient, client); kStatus status; kCheckArgs(!kIsNull(socket)); kCheck(kStream_Init(client, type, allocator)); obj->socket = kNULL; obj->writeTimeout = 0; obj->readTimeout = 0; obj->socketReadBufferSize = 0; obj->cancelQuery.function = kNULL; obj->cancelQuery.receiver = kNULL; obj->isCancelled = 0; obj->timedOut = kFALSE; obj->isSeekEnabled = kFALSE; kTry { kTest(kSocket_SetBlocking(socket, kFALSE)); obj->socket = socket; obj->socketReadBufferSize = xkSocket_ReadBuffer(socket); } kCatch(&status) { xkTcpClient_VRelease(client); kEndCatch(status); } return kOK; } kFx(kStatus) xkTcpClient_VRelease(kTcpClient client) { kObj(kTcpClient, client); kCheck(kObject_Destroy(obj->socket)); kCheck(kObject_FreeMem(client, obj->base.readBuffer)); kCheck(kObject_FreeMem(client, obj->base.writeBuffer)); kCheck(kStream_VRelease(client)); return kOK; } kFx(kStatus) kTcpClient_SetWriteBuffers(kTcpClient client, kSSize socketSize, kSSize clientSize) { kObj(kTcpClient, client); kCheck(kStream_Flush(client)); if (socketSize >= 0) { kCheck(kSocket_SetWriteBuffer(obj->socket, (kSize)socketSize)); } if (clientSize >= 0) { kCheck(kObject_FreeMemRef(client, &obj->base.writeBuffer)); obj->base.writeCapacity = 0; obj->base.writeBegin = 0; obj->base.writeEnd = 0; if (clientSize > 0) { kCheck(kObject_GetMem(client, (kSize)clientSize, &obj->base.writeBuffer)); obj->base.writeCapacity = (kSize)clientSize; obj->base.writeEnd = (kSize)clientSize; } } return kOK; } kFx(kStatus) kTcpClient_SetReadBuffers(kTcpClient client, kSSize socketSize, kSSize clientSize) { kObj(kTcpClient, client); kCheckState((obj->base.readEnd - obj->base.readBegin) == 0); if (socketSize >= 0) { kCheck(kSocket_SetReadBuffer(obj->socket, (kSize)socketSize)); obj->socketReadBufferSize = xkSocket_ReadBuffer(obj->socket); } if (clientSize >= 0) { kCheck(kObject_FreeMemRef(client, &obj->base.readBuffer)); obj->base.readCapacity = 0; obj->base.readBegin = 0; obj->base.readEnd = 0; if (clientSize > 0) { kCheck(kObject_GetMem(client, (kSize)clientSize, &obj->base.readBuffer)); obj->base.readCapacity = (kSize)clientSize; } } return kOK; } kFx(kStatus) kTcpClient_SetWriteTimeout(kTcpClient client, k64u timeout) { kObj(kTcpClient, client); obj->writeTimeout = timeout; return kOK; } kFx(kStatus) kTcpClient_SetReadTimeout(kTcpClient client, k64u timeout) { kObj(kTcpClient, client); obj->readTimeout = timeout; return kOK; } kFx(kStatus) kTcpClient_SetNoDelay(kTcpClient client, kBool noDelay) { kObj(kTcpClient, client); return kSocket_SetNoDelay(obj->socket, noDelay); } kFx(kStatus) kTcpClient_EnableSeek(kTcpClient client, kBool enabled) { kObj(kTcpClient, client); obj->isSeekEnabled = enabled; return kOK; } kFx(kStatus) kTcpClient_SetCancelHandler(kTcpClient client, kCallbackFx function, kPointer receiver) { kObj(kTcpClient, client); obj->cancelQuery.function = function; obj->cancelQuery.receiver = receiver; return kOK; } kFx(kStatus) kTcpClient_Cancel(kTcpClient client) { kObj(kTcpClient, client); kAtomic32s_Exchange(&obj->isCancelled, kTRUE); return kOK; } kFx(kStatus) kTcpClient_Connect(kTcpClient client, kIpAddress address, k32u port, k64u timeout) { kObj(kTcpClient, client); return kSocket_Connect(obj->socket, address, port, timeout); } kFx(kStatus) kTcpClient_BeginConnect(kTcpClient client, kIpAddress address, k32u port) { kObj(kTcpClient, client); return kSocket_BeginConnect(obj->socket, address, port); } kFx(kStatus) kTcpClient_EndConnect(kTcpClient client, k64u timeout) { kObj(kTcpClient, client); return kSocket_EndConnect(obj->socket, timeout); } kFx(kStatus) kTcpClient_Shutdown(kTcpClient client) { kObj(kTcpClient, client); return kSocket_Shutdown(obj->socket); } kFx(kStatus) kTcpClient_Wait(kTcpClient client, k64u timeout) { kObj(kTcpClient, client); if (kTcpClient_Available(client) > 0) { return kOK; } else { /* * FSS-288: Wind River APPNOTE_000531 suggests to use low water marks with Vx Works. */ #if defined(K_VX_KERNEL) xkSocket_SetReadLowWater(obj->socket, 1); #endif return xkSocket_WaitEx(obj->socket, kSOCKET_EVENT_READ, timeout); } } kFx(kStatus) xkTcpClient_VFlush(kTcpClient client) { kObj(kTcpClient, client); if (obj->base.writeBegin > 0) { kCheck(xkTcpClient_WriteAll(client, obj->base.writeBuffer, obj->base.writeBegin)); obj->base.writeBegin = 0; } if (obj->isSeekEnabled && (obj->base.readBegin > 0)) { kMemMove(obj->base.readBuffer, &obj->base.readBuffer[obj->base.readBegin], obj->base.readEnd - obj->base.readBegin); obj->base.readEnd = (obj->base.readEnd - obj->base.readBegin); obj->base.readBegin = 0; } return kOK; } kFx(kStatus) xkTcpClient_VFill(kTcpClient client) { kObj(kTcpClient, client); kSize mediumCount; kCheck(xkTcpClient_ReadAtLeast(client, &obj->base.readBuffer[0], 1, obj->base.readCapacity, &mediumCount)); obj->base.readBegin = 0; obj->base.readEnd = mediumCount; return kOK; } kFx(kStatus) xkTcpClient_VSeek(kTcpClient client, k64s offset, kSeekOrigin origin) { kObj(kTcpClient, client); switch (origin) { case kSEEK_ORIGIN_BEGIN: offset -= (k64s)kStream_BytesRead(client); break; case kSEEK_ORIGIN_CURRENT: break; case kSEEK_ORIGIN_END: return kERROR_PARAMETER; } if (offset >= 0) { kByte buffer[256]; //support forward read seek by reading and discarding while (offset > 0) { k64u chunkSize = kMin_((k64u)offset, sizeof(buffer)); kCheck(kStream_Read(client, buffer, (kSize)chunkSize)); offset -= chunkSize; } } else if (((k64s)obj->base.readBegin + offset) >= 0) { //support reverse read seek by adjusting buffer pointers... obj->base.readBegin += (kSSize) offset; } else { //...unless the seek destination is already out of buffer return kERROR_PARAMETER; } return kOK; } kFx(kStatus) xkTcpClient_VReadSomeImpl(kTcpClient client, void* buffer, kSize minCount, kSize maxCount, kSize* bytesRead) { kObj(kTcpClient, client); kByte* dest = (kByte*) buffer; kSize readCount = 0; kSize copyCount, mediumCount; kCheckArgs(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) { kCheck(xkTcpClient_ReadAtLeast(client, &dest[readCount], minCount-readCount, maxCount-readCount, &mediumCount)); obj->base.readBegin = obj->base.readEnd = 0; readCount += mediumCount; } else { //if seek is not enabled, prefer to write data at the beginning of the buffer if (!obj->isSeekEnabled || ((maxCount - readCount) > (obj->base.readCapacity - obj->base.readEnd))) { obj->base.readBegin = obj->base.readEnd = 0; } kCheck(xkTcpClient_ReadAtLeast(client, &obj->base.readBuffer[obj->base.readEnd], minCount - readCount, obj->base.readCapacity - obj->base.readEnd, &mediumCount)); copyCount = kMin_(mediumCount, maxCount - readCount); kCheck(kMemCopy(&dest[readCount], &obj->base.readBuffer[obj->base.readEnd], copyCount)); obj->base.readBegin += copyCount; obj->base.readEnd += mediumCount; readCount += copyCount; } } if (!kIsNull(bytesRead)) { *bytesRead = readCount; } return kOK; } kFx(kStatus) xkTcpClient_VWriteImpl(kTcpClient client, const void* buffer, kSize size) { kObj(kTcpClient, client); //if the internal write buffer already has content, but not enough free space, flush if ((obj->base.writeBegin > 0) && ((obj->base.writeEnd - obj->base.writeBegin) < size)) { kCheck(kStream_Flush(client)); } //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) { kCheck(xkTcpClient_WriteAll(client, (const kByte*) buffer, size)); } else { kCheck(kMemCopy(&obj->base.writeBuffer[obj->base.writeBegin], buffer, size)); obj->base.writeBegin += size; } return kOK; } kFx(kStatus) xkTcpClient_ReadAtLeast(kTcpClient client, kByte* buffer, kSize minCount, kSize maxCount, kSize* bytesRead) { kObj(kTcpClient, client); k64u startTime = (obj->readTimeout == 0) ? 0 : kTimer_Now(); kBool hasTimedOut = kFALSE; kStatus result; kSize totalRead = 0; kSize read; kCheck(kSocket_SetEvents(obj->socket, kSOCKET_EVENT_READ)); do { kCheck(kTcpClient_Status(client)); hasTimedOut = (obj->readTimeout == 0) ? kFALSE : ((kTimer_Now() - startTime) >= obj->readTimeout); /* * FSS-288: Wind River APPNOTE_000531 suggests to use low water marks with Vx Works. This decreases * CPU utilization when receiving data. */ #if defined(K_VX_KERNEL) { k32s lowWater = (k32s)(minCount - totalRead); lowWater = kMin_(lowWater, (k32s)obj->socketReadBufferSize/2); xkSocket_SetReadLowWater(obj->socket, lowWater); } #endif result = kSocket_Wait(obj->socket, xkTCP_CLIENT_CANCEL_QUERY_INTERVAL); if (result == kOK) { kCheck(kSocket_Read(obj->socket, &buffer[totalRead], maxCount-totalRead, &read)); obj->base.bytesRead += (k64u) read; totalRead += read; } else if ((result == kERROR_TIMEOUT) && !kIsNull(obj->cancelQuery.function)) { if (!kSuccess(obj->cancelQuery.function(obj->cancelQuery.receiver, client, kNULL))) { kAtomic32s_Exchange(&obj->isCancelled, kTRUE); return kERROR_ABORT; } } else if (result != kERROR_TIMEOUT) { return result; } } while ((totalRead < minCount) && !hasTimedOut); if (totalRead < minCount) { obj->timedOut = kTRUE; return kERROR_TIMEOUT; } *bytesRead = totalRead; return kOK; } kFx(kStatus) xkTcpClient_WriteAll(kTcpClient client, const kByte* buffer, kSize count) { kObj(kTcpClient, client); k64u startTime = (obj->writeTimeout == 0) ? 0 : kTimer_Now(); kBool hasTimedOut = kFALSE; kStatus result; kSize totalWritten = 0; kSize written; kChar peekByte; do { kCheck(kTcpClient_Status(client)); hasTimedOut = (obj->writeTimeout == 0) ? kFALSE : ((kTimer_Now() - startTime) >= obj->writeTimeout); result = xkSocket_WaitEx(obj->socket, kSOCKET_EVENT_WRITE, xkTCP_CLIENT_CANCEL_QUERY_INTERVAL); if (result == kOK) { kCheck(kSocket_Write(obj->socket, &buffer[totalWritten], count-totalWritten, &written)); obj->base.bytesWritten += (k64u) written; totalWritten += written; } else if (result == kERROR_TIMEOUT) { if (kSuccess(xkSocket_WaitEx(obj->socket, kSOCKET_EVENT_READ, 0))) { kCheck(xkSocket_Peek(obj->socket, &peekByte, sizeof(peekByte), kNULL)); } if (!kIsNull(obj->cancelQuery.function)) { if (!kSuccess(obj->cancelQuery.function(obj->cancelQuery.receiver, client, kNULL))) { kAtomic32s_Exchange(&obj->isCancelled, kTRUE); return kERROR_ABORT; } } } else { return result; } } while ((totalWritten < count) && !hasTimedOut); if (totalWritten < count) { obj->timedOut = kTRUE; return kERROR_TIMEOUT; } return kOK; } kFx(kSocket) kTcpClient_Socket(kTcpClient client) { kObj(kTcpClient, client); return obj->socket; } kFx(kSize) kTcpClient_Available(kTcpClient client) { kObj(kTcpClient, client); return obj->base.readEnd - obj->base.readBegin; } kFx(kStatus) kTcpClient_LocalEndPoint(kTcpClient client, kIpEndPoint* endPoint) { kObj(kTcpClient, client); return kSocket_LocalEndPoint(obj->socket, endPoint); } kFx(kStatus) kTcpClient_RemoteEndPoint(kTcpClient client, kIpEndPoint* endPoint) { kObj(kTcpClient, client); return kSocket_RemoteEndPoint(obj->socket, endPoint); } kFx(kStatus) kTcpClient_Status(kTcpClient client) { kObj(kTcpClient, client); if (kAtomic32s_Get(&obj->isCancelled)) { return kERROR_ABORT; } else if (obj->timedOut) { return kERROR_TIMEOUT; } else { return xkSocket_Status(obj->socket); } }