23#include <freerdp/config.h>
25#include <winpr/cast.h>
27#include <winpr/print.h>
28#include <winpr/stream.h>
30#include "rdpei_main.h"
31#include "../rdpei_common.h"
32#include <freerdp/channels/rdpei.h>
33#include <freerdp/server/rdpei.h>
44 STATE_WAITING_CLIENT_READY,
49struct s_rdpei_server_private
58 eRdpEiChannelState channelState;
65 UINT16 currentMsgType;
70 enum RdpEiState automataState;
73static UINT rdpei_server_open_channel(RdpeiServerContext* context)
75 DWORD error = ERROR_SUCCESS;
76 DWORD bytesReturned = 0;
77 PULONG pSessionId =
nullptr;
80 WINPR_ASSERT(context);
82 RdpeiServerPrivate* priv = context->priv;
85 if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId,
86 (LPSTR*)&pSessionId, &bytesReturned) == FALSE)
88 WLog_ERR(TAG,
"WTSQuerySessionInformationA failed!");
89 return ERROR_INTERNAL_ERROR;
92 DWORD sessionId = (DWORD)*pSessionId;
93 WTSFreeMemory(pSessionId);
96 WTSVirtualChannelOpenEx(sessionId, RDPEI_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC);
97 if (!priv->channelHandle)
99 error = GetLastError();
100 WLog_ERR(TAG,
"WTSVirtualChannelOpenEx failed with error %" PRIu32
"!", error);
104 const UINT32 channelId = WTSChannelGetIdByHandle(priv->channelHandle);
106 IFCALLRET(context->onChannelIdAssigned, status, context, channelId);
109 WLog_ERR(TAG,
"context->onChannelIdAssigned failed!");
110 return ERROR_INTERNAL_ERROR;
116static UINT rdpei_server_context_poll_int(RdpeiServerContext* context)
118 RdpeiServerPrivate* priv =
nullptr;
119 UINT error = ERROR_INTERNAL_ERROR;
121 WINPR_ASSERT(context);
122 priv = context->priv;
125 switch (priv->channelState)
128 error = rdpei_server_open_channel(context);
130 WLog_ERR(TAG,
"rdpei_server_open_channel failed with error %" PRIu32
"!", error);
132 priv->channelState = RDPEI_OPENED;
135 error = rdpei_server_handle_messages(context);
144static HANDLE rdpei_server_get_channel_handle(RdpeiServerContext* context)
146 RdpeiServerPrivate* priv =
nullptr;
147 void* buffer =
nullptr;
148 DWORD bytesReturned = 0;
149 HANDLE channelEvent =
nullptr;
151 WINPR_ASSERT(context);
152 priv = context->priv;
155 if (WTSVirtualChannelQuery(priv->channelHandle, WTSVirtualEventHandle, &buffer,
156 &bytesReturned) == TRUE)
158 if (bytesReturned ==
sizeof(HANDLE))
159 channelEvent = *(HANDLE*)buffer;
161 WTSFreeMemory(buffer);
167static DWORD WINAPI rdpei_server_thread_func(LPVOID arg)
169 RdpeiServerContext* context = (RdpeiServerContext*)arg;
170 RdpeiServerPrivate* priv =
nullptr;
171 HANDLE events[2] = WINPR_C_ARRAY_INIT;
173 UINT error = CHANNEL_RC_OK;
176 WINPR_ASSERT(context);
177 priv = context->priv;
180 events[nCount++] = priv->stopEvent;
182 while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0))
184 switch (priv->channelState)
187 error = rdpei_server_context_poll_int(context);
188 if (error == CHANNEL_RC_OK)
190 events[1] = rdpei_server_get_channel_handle(context);
195 status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
200 case WAIT_OBJECT_0 + 1:
202 error = rdpei_server_context_poll_int(context);
207 error = ERROR_INTERNAL_ERROR;
216 (void)WTSVirtualChannelClose(priv->channelHandle);
217 priv->channelHandle =
nullptr;
223static UINT rdpei_server_open(RdpeiServerContext* context)
225 RdpeiServerPrivate* priv =
nullptr;
227 priv = context->priv;
232 priv->stopEvent = CreateEvent(
nullptr, TRUE, FALSE,
nullptr);
233 if (!priv->stopEvent)
235 WLog_ERR(TAG,
"CreateEvent failed!");
236 return ERROR_INTERNAL_ERROR;
239 priv->thread = CreateThread(
nullptr, 0, rdpei_server_thread_func, context, 0,
nullptr);
242 WLog_ERR(TAG,
"CreateThread failed!");
243 (void)CloseHandle(priv->stopEvent);
244 priv->stopEvent =
nullptr;
245 return ERROR_INTERNAL_ERROR;
249 return CHANNEL_RC_OK;
252static UINT rdpei_server_close(RdpeiServerContext* context)
254 RdpeiServerPrivate* priv =
nullptr;
255 UINT error = CHANNEL_RC_OK;
257 priv = context->priv;
262 (void)SetEvent(priv->stopEvent);
264 if (WaitForSingleObject(priv->thread, INFINITE) == WAIT_FAILED)
266 error = GetLastError();
267 WLog_ERR(TAG,
"WaitForSingleObject failed with error %" PRIu32
"", error);
271 (void)CloseHandle(priv->thread);
272 (void)CloseHandle(priv->stopEvent);
273 priv->thread =
nullptr;
274 priv->stopEvent =
nullptr;
280RdpeiServerContext* rdpei_server_context_new(HANDLE vcm)
282 RdpeiServerContext* ret = calloc(1,
sizeof(*ret));
287 ret->Open = rdpei_server_open;
288 ret->Close = rdpei_server_close;
290 ret->priv = calloc(1,
sizeof(*ret->priv));
294 ret->priv->inputStream = Stream_New(
nullptr, 256);
295 if (!ret->priv->inputStream)
298 ret->priv->outputStream = Stream_New(
nullptr, 200);
299 if (!ret->priv->outputStream)
303 rdpei_server_context_reset(ret);
307 WINPR_PRAGMA_DIAG_PUSH
308 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
309 rdpei_server_context_free(ret);
310 WINPR_PRAGMA_DIAG_POP
319UINT rdpei_server_init(RdpeiServerContext* context)
321 RdpeiServerPrivate* priv = context->priv;
322 UINT error = rdpei_server_open_channel(context);
326 priv->eventHandle = rdpei_server_get_channel_handle(context);
327 if (!priv->eventHandle)
329 WLog_ERR(TAG,
"Failed to get channel handle!");
333 return CHANNEL_RC_OK;
336 (void)WTSVirtualChannelClose(priv->channelHandle);
337 priv->channelHandle = INVALID_HANDLE_VALUE;
338 return CHANNEL_RC_INITIALIZATION_ERROR;
341void rdpei_server_context_reset(RdpeiServerContext* context)
343 RdpeiServerPrivate* priv = context->priv;
345 priv->channelHandle = INVALID_HANDLE_VALUE;
346 priv->expectedBytes = RDPINPUT_HEADER_LENGTH;
347 priv->waitingHeaders = TRUE;
348 priv->automataState = STATE_INITIAL;
349 Stream_ResetPosition(priv->inputStream);
352void rdpei_server_context_free(RdpeiServerContext* context)
356 RdpeiServerPrivate* priv = context->priv;
359 if (priv->channelHandle && priv->channelHandle != INVALID_HANDLE_VALUE)
360 (void)WTSVirtualChannelClose(priv->channelHandle);
361 Stream_Free(priv->inputStream, TRUE);
362 Stream_Free(priv->outputStream, TRUE);
368HANDLE rdpei_server_get_event_handle(RdpeiServerContext* context)
370 return context->priv->eventHandle;
378static UINT read_cs_ready_message(RdpeiServerContext* context,
wStream* s)
380 UINT error = CHANNEL_RC_OK;
381 if (!Stream_CheckAndLogRequiredLength(TAG, s, 10))
382 return ERROR_INVALID_DATA;
384 Stream_Read_UINT32(s, context->protocolFlags);
385 Stream_Read_UINT32(s, context->clientVersion);
386 Stream_Read_UINT16(s, context->maxTouchPoints);
388 switch (context->clientVersion)
390 case RDPINPUT_PROTOCOL_V10:
391 case RDPINPUT_PROTOCOL_V101:
392 case RDPINPUT_PROTOCOL_V200:
393 case RDPINPUT_PROTOCOL_V300:
396 WLog_ERR(TAG,
"unhandled RPDEI protocol version 0x%" PRIx32
"", context->clientVersion);
400 IFCALLRET(context->onClientReady, error, context);
402 WLog_ERR(TAG,
"context->onClientReady failed with error %" PRIu32
"", error);
412static UINT read_touch_contact_data(RdpeiServerContext* context,
wStream* s,
415 WINPR_UNUSED(context);
416 if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
417 return ERROR_INVALID_DATA;
419 Stream_Read_UINT8(s, contactData->contactId);
420 if (!rdpei_read_2byte_unsigned(s, &contactData->fieldsPresent) ||
421 !rdpei_read_4byte_signed(s, &contactData->x) ||
422 !rdpei_read_4byte_signed(s, &contactData->y) ||
423 !rdpei_read_4byte_unsigned(s, &contactData->contactFlags))
425 WLog_ERR(TAG,
"rdpei_read_ failed!");
426 return ERROR_INTERNAL_ERROR;
429 if (contactData->fieldsPresent & CONTACT_DATA_CONTACTRECT_PRESENT)
431 if (!rdpei_read_2byte_signed(s, &contactData->contactRectLeft) ||
432 !rdpei_read_2byte_signed(s, &contactData->contactRectTop) ||
433 !rdpei_read_2byte_signed(s, &contactData->contactRectRight) ||
434 !rdpei_read_2byte_signed(s, &contactData->contactRectBottom))
436 WLog_ERR(TAG,
"rdpei_read_ failed!");
437 return ERROR_INTERNAL_ERROR;
441 if ((contactData->fieldsPresent & CONTACT_DATA_ORIENTATION_PRESENT) &&
442 !rdpei_read_4byte_unsigned(s, &contactData->orientation))
444 WLog_ERR(TAG,
"rdpei_read_ failed!");
445 return ERROR_INTERNAL_ERROR;
448 if ((contactData->fieldsPresent & CONTACT_DATA_PRESSURE_PRESENT) &&
449 !rdpei_read_4byte_unsigned(s, &contactData->pressure))
451 WLog_ERR(TAG,
"rdpei_read_ failed!");
452 return ERROR_INTERNAL_ERROR;
455 return CHANNEL_RC_OK;
458static UINT read_pen_contact(RdpeiServerContext* context,
wStream* s,
461 WINPR_UNUSED(context);
462 if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
463 return ERROR_INVALID_DATA;
465 Stream_Read_UINT8(s, contactData->deviceId);
466 if (!rdpei_read_2byte_unsigned(s, &contactData->fieldsPresent) ||
467 !rdpei_read_4byte_signed(s, &contactData->x) ||
468 !rdpei_read_4byte_signed(s, &contactData->y) ||
469 !rdpei_read_4byte_unsigned(s, &contactData->contactFlags))
471 WLog_ERR(TAG,
"rdpei_read_ failed!");
472 return ERROR_INTERNAL_ERROR;
475 if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT)
477 if (!rdpei_read_4byte_unsigned(s, &contactData->penFlags))
478 return ERROR_INVALID_DATA;
480 if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT)
482 if (!rdpei_read_4byte_unsigned(s, &contactData->pressure))
483 return ERROR_INVALID_DATA;
485 if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_ROTATION_PRESENT)
487 if (!rdpei_read_2byte_unsigned(s, &contactData->rotation))
488 return ERROR_INVALID_DATA;
490 if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTX_PRESENT)
492 if (!rdpei_read_2byte_signed(s, &contactData->tiltX))
493 return ERROR_INVALID_DATA;
495 if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTY_PRESENT)
497 if (!rdpei_read_2byte_signed(s, &contactData->tiltY))
498 return ERROR_INVALID_DATA;
501 return CHANNEL_RC_OK;
514 if (!rdpei_read_2byte_unsigned(s, &frame->contactCount) ||
515 !rdpei_read_8byte_unsigned(s, &frame->frameOffset))
517 WLog_ERR(TAG,
"rdpei_read_ failed!");
518 return ERROR_INTERNAL_ERROR;
522 if (!frame->contacts)
524 WLog_ERR(TAG,
"calloc failed!");
525 return CHANNEL_RC_NO_MEMORY;
528 for (UINT32 i = 0; i < frame->contactCount; i++, contact++)
530 if ((error = read_touch_contact_data(context, s, contact)))
532 WLog_ERR(TAG,
"read_touch_contact_data failed with error %" PRIu32
"!", error);
533 frame->contactCount = WINPR_ASSERTING_INT_CAST(UINT16, i);
534 touch_frame_reset(frame);
538 return CHANNEL_RC_OK;
546 if (!rdpei_read_2byte_unsigned(s, &frame->contactCount) ||
547 !rdpei_read_8byte_unsigned(s, &frame->frameOffset))
549 WLog_ERR(TAG,
"rdpei_read_ failed!");
550 return ERROR_INTERNAL_ERROR;
554 if (!frame->contacts)
556 WLog_ERR(TAG,
"calloc failed!");
557 return CHANNEL_RC_NO_MEMORY;
560 for (UINT32 i = 0; i < frame->contactCount; i++, contact++)
562 if ((error = read_pen_contact(context, s, contact)))
564 WLog_ERR(TAG,
"read_touch_contact_data failed with error %" PRIu32
"!", error);
565 frame->contactCount = WINPR_ASSERTING_INT_CAST(UINT16, i);
567 pen_frame_reset(frame);
571 return CHANNEL_RC_OK;
579static UINT read_touch_event(RdpeiServerContext* context,
wStream* s)
581 UINT16 frameCount = 0;
583 WINPR_ASSERT(context);
584 WINPR_ASSERT(context->priv);
590 UINT error = CHANNEL_RC_OK;
592 if (!rdpei_read_4byte_unsigned(s, &event->encodeTime) ||
593 !rdpei_read_2byte_unsigned(s, &frameCount))
595 WLog_ERR(TAG,
"rdpei_read_ failed!");
596 return ERROR_INTERNAL_ERROR;
599 event->frameCount = frameCount;
603 WLog_ERR(TAG,
"calloc failed!");
604 return CHANNEL_RC_NO_MEMORY;
607 for (UINT32 i = 0; i < frameCount; i++, frame++)
609 if ((error = read_touch_frame(context, s, frame)))
611 WLog_ERR(TAG,
"read_touch_contact_data failed with error %" PRIu32
"!", error);
612 event->frameCount = WINPR_ASSERTING_INT_CAST(UINT16, i);
618 IFCALLRET(context->onTouchEvent, error, context, event);
620 WLog_ERR(TAG,
"context->onTouchEvent failed with error %" PRIu32
"", error);
623 touch_event_reset(event);
627static UINT read_pen_event(RdpeiServerContext* context,
wStream* s)
629 UINT16 frameCount = 0;
632 UINT error = CHANNEL_RC_OK;
634 if (!rdpei_read_4byte_unsigned(s, &event->encodeTime) ||
635 !rdpei_read_2byte_unsigned(s, &frameCount))
637 WLog_ERR(TAG,
"rdpei_read_ failed!");
638 return ERROR_INTERNAL_ERROR;
641 event->frameCount = frameCount;
645 WLog_ERR(TAG,
"calloc failed!");
646 return CHANNEL_RC_NO_MEMORY;
649 for (UINT32 i = 0; i < frameCount; i++, frame++)
651 if ((error = read_pen_frame(context, s, frame)))
653 WLog_ERR(TAG,
"read_pen_frame failed with error %" PRIu32
"!", error);
654 event->frameCount = WINPR_ASSERTING_INT_CAST(UINT16, i);
660 IFCALLRET(context->onPenEvent, error, context, event);
662 WLog_ERR(TAG,
"context->onPenEvent failed with error %" PRIu32
"", error);
665 pen_event_reset(event);
674static UINT read_dismiss_hovering_contact(RdpeiServerContext* context,
wStream* s)
677 UINT error = CHANNEL_RC_OK;
679 if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
680 return ERROR_INVALID_DATA;
682 Stream_Read_UINT8(s, contactId);
684 IFCALLRET(context->onTouchReleased, error, context, contactId);
686 WLog_ERR(TAG,
"context->onTouchReleased failed with error %" PRIu32
"", error);
696UINT rdpei_server_handle_messages(RdpeiServerContext* context)
698 DWORD bytesReturned = 0;
700 WINPR_ASSERT(context);
702 RdpeiServerPrivate* priv = context->priv;
705 wStream* s = priv->inputStream;
706 UINT error = CHANNEL_RC_OK;
708 if (!WTSVirtualChannelRead(priv->channelHandle, 0, Stream_Pointer(s), priv->expectedBytes,
711 if (GetLastError() == ERROR_NO_DATA)
712 return ERROR_READ_FAULT;
714 WLog_DBG(TAG,
"channel connection closed");
715 return CHANNEL_RC_OK;
717 priv->expectedBytes -= bytesReturned;
718 Stream_Seek(s, bytesReturned);
720 if (priv->expectedBytes)
721 return CHANNEL_RC_OK;
723 Stream_SealLength(s);
724 Stream_ResetPosition(s);
726 if (priv->waitingHeaders)
729 priv->currentMsgType = Stream_Get_UINT16(s);
730 const UINT32 pduLen = Stream_Get_UINT32(s);
732 if (pduLen < RDPINPUT_HEADER_LENGTH)
734 WLog_ERR(TAG,
"invalid pduLength %" PRIu32
"", pduLen);
735 return ERROR_INVALID_DATA;
738 if (pduLen > RDPINPUT_MAX_PDU_LENGTH)
740 WLog_ERR(TAG,
"invalid pduLength %" PRIu32
" > RDPINPUT_MAX_PDU_LENGTH(%llu)", pduLen,
741 RDPINPUT_MAX_PDU_LENGTH);
742 return ERROR_INVALID_DATA;
745 priv->expectedBytes = pduLen - RDPINPUT_HEADER_LENGTH;
746 priv->waitingHeaders = FALSE;
747 Stream_ResetPosition(s);
748 if (priv->expectedBytes)
750 if (!Stream_EnsureCapacity(s, priv->expectedBytes))
752 WLog_ERR(TAG,
"Stream_EnsureCapacity failed!");
753 return CHANNEL_RC_NO_MEMORY;
755 return CHANNEL_RC_OK;
760 switch (priv->currentMsgType)
762 case EVENTID_CS_READY:
763 if (priv->automataState != STATE_WAITING_CLIENT_READY)
765 WLog_ERR(TAG,
"not expecting a CS_READY packet in this state(%u)",
766 priv->automataState);
767 return ERROR_INVALID_STATE;
770 if ((error = read_cs_ready_message(context, s)))
772 WLog_ERR(TAG,
"read_cs_ready_message failed with error %" PRIu32
"", error);
775 priv->automataState = STATE_WAITING_FRAME;
779 if ((error = read_touch_event(context, s)))
781 WLog_ERR(TAG,
"read_touch_event failed with error %" PRIu32
"", error);
785 case EVENTID_DISMISS_HOVERING_CONTACT:
786 if ((error = read_dismiss_hovering_contact(context, s)))
788 WLog_ERR(TAG,
"read_dismiss_hovering_contact failed with error %" PRIu32
"", error);
793 if ((error = read_pen_event(context, s)))
795 WLog_ERR(TAG,
"read_pen_event failed with error %" PRIu32
"", error);
800 WLog_ERR(TAG,
"unexpected message type 0x%" PRIx16
"", priv->currentMsgType);
803 Stream_ResetPosition(s);
804 priv->waitingHeaders = TRUE;
805 priv->expectedBytes = RDPINPUT_HEADER_LENGTH;
814UINT rdpei_server_send_sc_ready(RdpeiServerContext* context, UINT32 version, UINT32 features)
817 RdpeiServerPrivate* priv = context->priv;
820 if (priv->automataState != STATE_INITIAL)
822 WLog_ERR(TAG,
"called from unexpected state %u", priv->automataState);
823 return ERROR_INVALID_STATE;
826 Stream_ResetPosition(priv->outputStream);
828 if (version >= RDPINPUT_PROTOCOL_V300)
831 if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH + pduLen))
833 WLog_ERR(TAG,
"Stream_EnsureCapacity failed!");
834 return CHANNEL_RC_NO_MEMORY;
837 Stream_Write_UINT16(priv->outputStream, EVENTID_SC_READY);
838 Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH + pduLen);
839 Stream_Write_UINT32(priv->outputStream, version);
840 if (version >= RDPINPUT_PROTOCOL_V300)
841 Stream_Write_UINT32(priv->outputStream, features);
843 const size_t pos = Stream_GetPosition(priv->outputStream);
845 WINPR_ASSERT(pos <= UINT32_MAX);
846 if (!WTSVirtualChannelWrite(priv->channelHandle, Stream_BufferAs(priv->outputStream,
char),
847 (ULONG)pos, &written))
849 WLog_ERR(TAG,
"WTSVirtualChannelWrite failed!");
850 return ERROR_INTERNAL_ERROR;
853 priv->automataState = STATE_WAITING_CLIENT_READY;
854 return CHANNEL_RC_OK;
862UINT rdpei_server_suspend(RdpeiServerContext* context)
865 RdpeiServerPrivate* priv = context->priv;
867 switch (priv->automataState)
869 case STATE_SUSPENDED:
870 WLog_ERR(TAG,
"already suspended");
871 return CHANNEL_RC_OK;
872 case STATE_WAITING_FRAME:
875 WLog_ERR(TAG,
"called from unexpected state %u", priv->automataState);
876 return ERROR_INVALID_STATE;
879 Stream_ResetPosition(priv->outputStream);
880 if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH))
882 WLog_ERR(TAG,
"Stream_EnsureCapacity failed!");
883 return CHANNEL_RC_NO_MEMORY;
886 Stream_Write_UINT16(priv->outputStream, EVENTID_SUSPEND_TOUCH);
887 Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH);
889 const size_t pos = Stream_GetPosition(priv->outputStream);
891 WINPR_ASSERT(pos <= UINT32_MAX);
892 if (!WTSVirtualChannelWrite(priv->channelHandle, Stream_BufferAs(priv->outputStream,
char),
893 (ULONG)pos, &written))
895 WLog_ERR(TAG,
"WTSVirtualChannelWrite failed!");
896 return ERROR_INTERNAL_ERROR;
899 priv->automataState = STATE_SUSPENDED;
900 return CHANNEL_RC_OK;
908UINT rdpei_server_resume(RdpeiServerContext* context)
911 RdpeiServerPrivate* priv = context->priv;
913 switch (priv->automataState)
915 case STATE_WAITING_FRAME:
916 WLog_ERR(TAG,
"not suspended");
917 return CHANNEL_RC_OK;
918 case STATE_SUSPENDED:
921 WLog_ERR(TAG,
"called from unexpected state %u", priv->automataState);
922 return ERROR_INVALID_STATE;
925 Stream_ResetPosition(priv->outputStream);
926 if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH))
928 WLog_ERR(TAG,
"Stream_EnsureCapacity failed!");
929 return CHANNEL_RC_NO_MEMORY;
932 Stream_Write_UINT16(priv->outputStream, EVENTID_RESUME_TOUCH);
933 Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH);
935 const size_t pos = Stream_GetPosition(priv->outputStream);
937 WINPR_ASSERT(pos <= UINT32_MAX);
938 if (!WTSVirtualChannelWrite(priv->channelHandle, Stream_BufferAs(priv->outputStream,
char),
939 (ULONG)pos, &written))
941 WLog_ERR(TAG,
"WTSVirtualChannelWrite failed!");
942 return ERROR_INTERNAL_ERROR;
945 priv->automataState = STATE_WAITING_FRAME;
946 return CHANNEL_RC_OK;