FreeRDP
server/rdpei_main.c
1 
23 #include <freerdp/config.h>
24 
25 #include <winpr/crt.h>
26 #include <winpr/print.h>
27 #include <winpr/stream.h>
28 
29 #include "rdpei_main.h"
30 #include "../rdpei_common.h"
31 #include <freerdp/channels/rdpei.h>
32 #include <freerdp/server/rdpei.h>
33 
34 enum RdpEiState
35 {
36  STATE_INITIAL,
37  STATE_WAITING_CLIENT_READY,
38  STATE_WAITING_FRAME,
39  STATE_SUSPENDED,
40 };
41 
42 struct s_rdpei_server_private
43 {
44  HANDLE channelHandle;
45  HANDLE eventHandle;
46 
47  UINT32 expectedBytes;
48  BOOL waitingHeaders;
49  wStream* inputStream;
50  wStream* outputStream;
51 
52  UINT16 currentMsgType;
53 
54  RDPINPUT_TOUCH_EVENT touchEvent;
55  RDPINPUT_PEN_EVENT penEvent;
56 
57  enum RdpEiState automataState;
58 };
59 
60 RdpeiServerContext* rdpei_server_context_new(HANDLE vcm)
61 {
62  RdpeiServerContext* ret = calloc(1, sizeof(*ret));
63  RdpeiServerPrivate* priv = NULL;
64 
65  if (!ret)
66  return NULL;
67 
68  ret->priv = priv = calloc(1, sizeof(*ret->priv));
69  if (!priv)
70  goto fail;
71 
72  priv->inputStream = Stream_New(NULL, 256);
73  if (!priv->inputStream)
74  goto fail;
75 
76  priv->outputStream = Stream_New(NULL, 200);
77  if (!priv->inputStream)
78  goto fail;
79 
80  ret->vcm = vcm;
81  rdpei_server_context_reset(ret);
82  return ret;
83 
84 fail:
85  WINPR_PRAGMA_DIAG_PUSH
86  WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
87  rdpei_server_context_free(ret);
88  WINPR_PRAGMA_DIAG_POP
89  return NULL;
90 }
91 
97 UINT rdpei_server_init(RdpeiServerContext* context)
98 {
99  void* buffer = NULL;
100  DWORD bytesReturned = 0;
101  RdpeiServerPrivate* priv = context->priv;
102  UINT32 channelId = 0;
103  BOOL status = TRUE;
104 
105  priv->channelHandle = WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, RDPEI_DVC_CHANNEL_NAME,
106  WTS_CHANNEL_OPTION_DYNAMIC);
107  if (!priv->channelHandle)
108  {
109  WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed!");
110  return CHANNEL_RC_INITIALIZATION_ERROR;
111  }
112 
113  channelId = WTSChannelGetIdByHandle(priv->channelHandle);
114 
115  IFCALLRET(context->onChannelIdAssigned, status, context, channelId);
116  if (!status)
117  {
118  WLog_ERR(TAG, "context->onChannelIdAssigned failed!");
119  goto out_close;
120  }
121 
122  if (!WTSVirtualChannelQuery(priv->channelHandle, WTSVirtualEventHandle, &buffer,
123  &bytesReturned) ||
124  (bytesReturned != sizeof(HANDLE)))
125  {
126  WLog_ERR(TAG,
127  "WTSVirtualChannelQuery failed or invalid invalid returned size(%" PRIu32 ")!",
128  bytesReturned);
129  if (buffer)
130  WTSFreeMemory(buffer);
131  goto out_close;
132  }
133  CopyMemory(&priv->eventHandle, buffer, sizeof(HANDLE));
134  WTSFreeMemory(buffer);
135 
136  return CHANNEL_RC_OK;
137 
138 out_close:
139  (void)WTSVirtualChannelClose(priv->channelHandle);
140  return CHANNEL_RC_INITIALIZATION_ERROR;
141 }
142 
143 void rdpei_server_context_reset(RdpeiServerContext* context)
144 {
145  RdpeiServerPrivate* priv = context->priv;
146 
147  priv->channelHandle = INVALID_HANDLE_VALUE;
148  priv->expectedBytes = RDPINPUT_HEADER_LENGTH;
149  priv->waitingHeaders = TRUE;
150  priv->automataState = STATE_INITIAL;
151  Stream_SetPosition(priv->inputStream, 0);
152 }
153 
154 void rdpei_server_context_free(RdpeiServerContext* context)
155 {
156  RdpeiServerPrivate* priv = NULL;
157 
158  if (!context)
159  return;
160  priv = context->priv;
161  if (priv)
162  {
163  if (priv->channelHandle != INVALID_HANDLE_VALUE)
164  (void)WTSVirtualChannelClose(priv->channelHandle);
165  Stream_Free(priv->inputStream, TRUE);
166  }
167  free(priv);
168  free(context);
169 }
170 
171 HANDLE rdpei_server_get_event_handle(RdpeiServerContext* context)
172 {
173  return context->priv->eventHandle;
174 }
175 
181 static UINT read_cs_ready_message(RdpeiServerContext* context, wStream* s)
182 {
183  UINT error = CHANNEL_RC_OK;
184  if (!Stream_CheckAndLogRequiredLength(TAG, s, 10))
185  return ERROR_INVALID_DATA;
186 
187  Stream_Read_UINT32(s, context->protocolFlags);
188  Stream_Read_UINT32(s, context->clientVersion);
189  Stream_Read_UINT16(s, context->maxTouchPoints);
190 
191  switch (context->clientVersion)
192  {
193  case RDPINPUT_PROTOCOL_V10:
194  case RDPINPUT_PROTOCOL_V101:
195  case RDPINPUT_PROTOCOL_V200:
196  case RDPINPUT_PROTOCOL_V300:
197  break;
198  default:
199  WLog_ERR(TAG, "unhandled RPDEI protocol version 0x%" PRIx32 "", context->clientVersion);
200  break;
201  }
202 
203  IFCALLRET(context->onClientReady, error, context);
204  if (error)
205  WLog_ERR(TAG, "context->onClientReady failed with error %" PRIu32 "", error);
206 
207  return error;
208 }
209 
215 static UINT read_touch_contact_data(RdpeiServerContext* context, wStream* s,
216  RDPINPUT_CONTACT_DATA* contactData)
217 {
218  WINPR_UNUSED(context);
219  if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
220  return ERROR_INVALID_DATA;
221 
222  Stream_Read_UINT8(s, contactData->contactId);
223  if (!rdpei_read_2byte_unsigned(s, &contactData->fieldsPresent) ||
224  !rdpei_read_4byte_signed(s, &contactData->x) ||
225  !rdpei_read_4byte_signed(s, &contactData->y) ||
226  !rdpei_read_4byte_unsigned(s, &contactData->contactFlags))
227  {
228  WLog_ERR(TAG, "rdpei_read_ failed!");
229  return ERROR_INTERNAL_ERROR;
230  }
231 
232  if (contactData->fieldsPresent & CONTACT_DATA_CONTACTRECT_PRESENT)
233  {
234  if (!rdpei_read_2byte_signed(s, &contactData->contactRectLeft) ||
235  !rdpei_read_2byte_signed(s, &contactData->contactRectTop) ||
236  !rdpei_read_2byte_signed(s, &contactData->contactRectRight) ||
237  !rdpei_read_2byte_signed(s, &contactData->contactRectBottom))
238  {
239  WLog_ERR(TAG, "rdpei_read_ failed!");
240  return ERROR_INTERNAL_ERROR;
241  }
242  }
243 
244  if ((contactData->fieldsPresent & CONTACT_DATA_ORIENTATION_PRESENT) &&
245  !rdpei_read_4byte_unsigned(s, &contactData->orientation))
246  {
247  WLog_ERR(TAG, "rdpei_read_ failed!");
248  return ERROR_INTERNAL_ERROR;
249  }
250 
251  if ((contactData->fieldsPresent & CONTACT_DATA_PRESSURE_PRESENT) &&
252  !rdpei_read_4byte_unsigned(s, &contactData->pressure))
253  {
254  WLog_ERR(TAG, "rdpei_read_ failed!");
255  return ERROR_INTERNAL_ERROR;
256  }
257 
258  return CHANNEL_RC_OK;
259 }
260 
261 static UINT read_pen_contact(RdpeiServerContext* context, wStream* s,
262  RDPINPUT_PEN_CONTACT* contactData)
263 {
264  WINPR_UNUSED(context);
265  if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
266  return ERROR_INVALID_DATA;
267 
268  Stream_Read_UINT8(s, contactData->deviceId);
269  if (!rdpei_read_2byte_unsigned(s, &contactData->fieldsPresent) ||
270  !rdpei_read_4byte_signed(s, &contactData->x) ||
271  !rdpei_read_4byte_signed(s, &contactData->y) ||
272  !rdpei_read_4byte_unsigned(s, &contactData->contactFlags))
273  {
274  WLog_ERR(TAG, "rdpei_read_ failed!");
275  return ERROR_INTERNAL_ERROR;
276  }
277 
278  if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT)
279  {
280  if (!rdpei_read_4byte_unsigned(s, &contactData->penFlags))
281  return ERROR_INVALID_DATA;
282  }
283  if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT)
284  {
285  if (!rdpei_read_4byte_unsigned(s, &contactData->pressure))
286  return ERROR_INVALID_DATA;
287  }
288  if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_ROTATION_PRESENT)
289  {
290  if (!rdpei_read_2byte_unsigned(s, &contactData->rotation))
291  return ERROR_INVALID_DATA;
292  }
293  if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTX_PRESENT)
294  {
295  if (!rdpei_read_2byte_signed(s, &contactData->tiltX))
296  return ERROR_INVALID_DATA;
297  }
298  if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTY_PRESENT)
299  {
300  if (!rdpei_read_2byte_signed(s, &contactData->tiltY))
301  return ERROR_INVALID_DATA;
302  }
303 
304  return CHANNEL_RC_OK;
305 }
306 
312 static UINT read_touch_frame(RdpeiServerContext* context, wStream* s, RDPINPUT_TOUCH_FRAME* frame)
313 {
314  RDPINPUT_CONTACT_DATA* contact = NULL;
315  UINT error = 0;
316 
317  if (!rdpei_read_2byte_unsigned(s, &frame->contactCount) ||
318  !rdpei_read_8byte_unsigned(s, &frame->frameOffset))
319  {
320  WLog_ERR(TAG, "rdpei_read_ failed!");
321  return ERROR_INTERNAL_ERROR;
322  }
323 
324  frame->contacts = contact = calloc(frame->contactCount, sizeof(RDPINPUT_CONTACT_DATA));
325  if (!frame->contacts)
326  {
327  WLog_ERR(TAG, "calloc failed!");
328  return CHANNEL_RC_NO_MEMORY;
329  }
330 
331  for (UINT32 i = 0; i < frame->contactCount; i++, contact++)
332  {
333  if ((error = read_touch_contact_data(context, s, contact)))
334  {
335  WLog_ERR(TAG, "read_touch_contact_data failed with error %" PRIu32 "!", error);
336  frame->contactCount = i;
337  touch_frame_reset(frame);
338  return error;
339  }
340  }
341  return CHANNEL_RC_OK;
342 }
343 
344 static UINT read_pen_frame(RdpeiServerContext* context, wStream* s, RDPINPUT_PEN_FRAME* frame)
345 {
346  RDPINPUT_PEN_CONTACT* contact = NULL;
347  UINT error = 0;
348 
349  if (!rdpei_read_2byte_unsigned(s, &frame->contactCount) ||
350  !rdpei_read_8byte_unsigned(s, &frame->frameOffset))
351  {
352  WLog_ERR(TAG, "rdpei_read_ failed!");
353  return ERROR_INTERNAL_ERROR;
354  }
355 
356  frame->contacts = contact = calloc(frame->contactCount, sizeof(RDPINPUT_PEN_CONTACT));
357  if (!frame->contacts)
358  {
359  WLog_ERR(TAG, "calloc failed!");
360  return CHANNEL_RC_NO_MEMORY;
361  }
362 
363  for (UINT32 i = 0; i < frame->contactCount; i++, contact++)
364  {
365  if ((error = read_pen_contact(context, s, contact)))
366  {
367  WLog_ERR(TAG, "read_touch_contact_data failed with error %" PRIu32 "!", error);
368  frame->contactCount = i;
369  pen_frame_reset(frame);
370  return error;
371  }
372  }
373  return CHANNEL_RC_OK;
374 }
375 
381 static UINT read_touch_event(RdpeiServerContext* context, wStream* s)
382 {
383  UINT16 frameCount = 0;
384  RDPINPUT_TOUCH_EVENT* event = &context->priv->touchEvent;
385  RDPINPUT_TOUCH_FRAME* frame = NULL;
386  UINT error = CHANNEL_RC_OK;
387 
388  if (!rdpei_read_4byte_unsigned(s, &event->encodeTime) ||
389  !rdpei_read_2byte_unsigned(s, &frameCount))
390  {
391  WLog_ERR(TAG, "rdpei_read_ failed!");
392  return ERROR_INTERNAL_ERROR;
393  }
394 
395  event->frameCount = frameCount;
396  event->frames = frame = calloc(event->frameCount, sizeof(RDPINPUT_TOUCH_FRAME));
397  if (!event->frames)
398  {
399  WLog_ERR(TAG, "calloc failed!");
400  return CHANNEL_RC_NO_MEMORY;
401  }
402 
403  for (UINT32 i = 0; i < frameCount; i++, frame++)
404  {
405  if ((error = read_touch_frame(context, s, frame)))
406  {
407  WLog_ERR(TAG, "read_touch_contact_data failed with error %" PRIu32 "!", error);
408  event->frameCount = i;
409  goto out_cleanup;
410  }
411  }
412 
413  IFCALLRET(context->onTouchEvent, error, context, event);
414  if (error)
415  WLog_ERR(TAG, "context->onTouchEvent failed with error %" PRIu32 "", error);
416 
417 out_cleanup:
418  touch_event_reset(event);
419  return error;
420 }
421 
422 static UINT read_pen_event(RdpeiServerContext* context, wStream* s)
423 {
424  UINT16 frameCount = 0;
425  RDPINPUT_PEN_EVENT* event = &context->priv->penEvent;
426  RDPINPUT_PEN_FRAME* frame = NULL;
427  UINT error = CHANNEL_RC_OK;
428 
429  if (!rdpei_read_4byte_unsigned(s, &event->encodeTime) ||
430  !rdpei_read_2byte_unsigned(s, &frameCount))
431  {
432  WLog_ERR(TAG, "rdpei_read_ failed!");
433  return ERROR_INTERNAL_ERROR;
434  }
435 
436  event->frameCount = frameCount;
437  event->frames = frame = calloc(event->frameCount, sizeof(RDPINPUT_PEN_FRAME));
438  if (!event->frames)
439  {
440  WLog_ERR(TAG, "calloc failed!");
441  return CHANNEL_RC_NO_MEMORY;
442  }
443 
444  for (UINT32 i = 0; i < frameCount; i++, frame++)
445  {
446  if ((error = read_pen_frame(context, s, frame)))
447  {
448  WLog_ERR(TAG, "read_pen_frame failed with error %" PRIu32 "!", error);
449  event->frameCount = i;
450  goto out_cleanup;
451  }
452  }
453 
454  IFCALLRET(context->onPenEvent, error, context, event);
455  if (error)
456  WLog_ERR(TAG, "context->onPenEvent failed with error %" PRIu32 "", error);
457 
458 out_cleanup:
459  pen_event_reset(event);
460  return error;
461 }
462 
468 static UINT read_dismiss_hovering_contact(RdpeiServerContext* context, wStream* s)
469 {
470  BYTE contactId = 0;
471  UINT error = CHANNEL_RC_OK;
472 
473  if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
474  return ERROR_INVALID_DATA;
475 
476  Stream_Read_UINT8(s, contactId);
477 
478  IFCALLRET(context->onTouchReleased, error, context, contactId);
479  if (error)
480  WLog_ERR(TAG, "context->onTouchReleased failed with error %" PRIu32 "", error);
481 
482  return error;
483 }
484 
490 UINT rdpei_server_handle_messages(RdpeiServerContext* context)
491 {
492  DWORD bytesReturned = 0;
493  RdpeiServerPrivate* priv = context->priv;
494  wStream* s = priv->inputStream;
495  UINT error = CHANNEL_RC_OK;
496 
497  if (!WTSVirtualChannelRead(priv->channelHandle, 0, Stream_Pointer(s), priv->expectedBytes,
498  &bytesReturned))
499  {
500  if (GetLastError() == ERROR_NO_DATA)
501  return ERROR_READ_FAULT;
502 
503  WLog_DBG(TAG, "channel connection closed");
504  return CHANNEL_RC_OK;
505  }
506  priv->expectedBytes -= bytesReturned;
507  Stream_Seek(s, bytesReturned);
508 
509  if (priv->expectedBytes)
510  return CHANNEL_RC_OK;
511 
512  Stream_SealLength(s);
513  Stream_SetPosition(s, 0);
514 
515  if (priv->waitingHeaders)
516  {
517  UINT32 pduLen = 0;
518 
519  /* header case */
520  Stream_Read_UINT16(s, priv->currentMsgType);
521  Stream_Read_UINT16(s, pduLen);
522 
523  if (pduLen < RDPINPUT_HEADER_LENGTH)
524  {
525  WLog_ERR(TAG, "invalid pduLength %" PRIu32 "", pduLen);
526  return ERROR_INVALID_DATA;
527  }
528  priv->expectedBytes = pduLen - RDPINPUT_HEADER_LENGTH;
529  priv->waitingHeaders = FALSE;
530  Stream_SetPosition(s, 0);
531  if (priv->expectedBytes)
532  {
533  if (!Stream_EnsureCapacity(s, priv->expectedBytes))
534  {
535  WLog_ERR(TAG, "Stream_EnsureCapacity failed!");
536  return CHANNEL_RC_NO_MEMORY;
537  }
538  return CHANNEL_RC_OK;
539  }
540  }
541 
542  /* when here we have the header + the body */
543  switch (priv->currentMsgType)
544  {
545  case EVENTID_CS_READY:
546  if (priv->automataState != STATE_WAITING_CLIENT_READY)
547  {
548  WLog_ERR(TAG, "not expecting a CS_READY packet in this state(%d)",
549  priv->automataState);
550  return ERROR_INVALID_STATE;
551  }
552 
553  if ((error = read_cs_ready_message(context, s)))
554  {
555  WLog_ERR(TAG, "read_cs_ready_message failed with error %" PRIu32 "", error);
556  return error;
557  }
558  break;
559 
560  case EVENTID_TOUCH:
561  if ((error = read_touch_event(context, s)))
562  {
563  WLog_ERR(TAG, "read_touch_event failed with error %" PRIu32 "", error);
564  return error;
565  }
566  break;
567  case EVENTID_DISMISS_HOVERING_CONTACT:
568  if ((error = read_dismiss_hovering_contact(context, s)))
569  {
570  WLog_ERR(TAG, "read_dismiss_hovering_contact failed with error %" PRIu32 "", error);
571  return error;
572  }
573  break;
574  case EVENTID_PEN:
575  if ((error = read_pen_event(context, s)))
576  {
577  WLog_ERR(TAG, "read_pen_event failed with error %" PRIu32 "", error);
578  return error;
579  }
580  break;
581  default:
582  WLog_ERR(TAG, "unexpected message type 0x%" PRIx16 "", priv->currentMsgType);
583  }
584 
585  Stream_SetPosition(s, 0);
586  priv->waitingHeaders = TRUE;
587  priv->expectedBytes = RDPINPUT_HEADER_LENGTH;
588  return error;
589 }
590 
596 UINT rdpei_server_send_sc_ready(RdpeiServerContext* context, UINT32 version, UINT32 features)
597 {
598  ULONG written = 0;
599  RdpeiServerPrivate* priv = context->priv;
600  UINT32 pduLen = 4;
601 
602  if (priv->automataState != STATE_INITIAL)
603  {
604  WLog_ERR(TAG, "called from unexpected state %d", priv->automataState);
605  return ERROR_INVALID_STATE;
606  }
607 
608  Stream_SetPosition(priv->outputStream, 0);
609 
610  if (version >= RDPINPUT_PROTOCOL_V300)
611  pduLen += 4;
612 
613  if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH + pduLen))
614  {
615  WLog_ERR(TAG, "Stream_EnsureCapacity failed!");
616  return CHANNEL_RC_NO_MEMORY;
617  }
618 
619  Stream_Write_UINT16(priv->outputStream, EVENTID_SC_READY);
620  Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH + pduLen);
621  Stream_Write_UINT32(priv->outputStream, version);
622  if (version >= RDPINPUT_PROTOCOL_V300)
623  Stream_Write_UINT32(priv->outputStream, features);
624 
625  const size_t pos = Stream_GetPosition(priv->outputStream);
626 
627  WINPR_ASSERT(pos <= UINT32_MAX);
628  if (!WTSVirtualChannelWrite(priv->channelHandle, Stream_BufferAs(priv->outputStream, char),
629  (ULONG)pos, &written))
630  {
631  WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
632  return ERROR_INTERNAL_ERROR;
633  }
634 
635  priv->automataState = STATE_WAITING_CLIENT_READY;
636  return CHANNEL_RC_OK;
637 }
638 
644 UINT rdpei_server_suspend(RdpeiServerContext* context)
645 {
646  ULONG written = 0;
647  RdpeiServerPrivate* priv = context->priv;
648 
649  switch (priv->automataState)
650  {
651  case STATE_SUSPENDED:
652  WLog_ERR(TAG, "already suspended");
653  return CHANNEL_RC_OK;
654  case STATE_WAITING_FRAME:
655  break;
656  default:
657  WLog_ERR(TAG, "called from unexpected state %d", priv->automataState);
658  return ERROR_INVALID_STATE;
659  }
660 
661  Stream_SetPosition(priv->outputStream, 0);
662  if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH))
663  {
664  WLog_ERR(TAG, "Stream_EnsureCapacity failed!");
665  return CHANNEL_RC_NO_MEMORY;
666  }
667 
668  Stream_Write_UINT16(priv->outputStream, EVENTID_SUSPEND_TOUCH);
669  Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH);
670 
671  const size_t pos = Stream_GetPosition(priv->outputStream);
672 
673  WINPR_ASSERT(pos <= UINT32_MAX);
674  if (!WTSVirtualChannelWrite(priv->channelHandle, Stream_BufferAs(priv->outputStream, char),
675  (ULONG)pos, &written))
676  {
677  WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
678  return ERROR_INTERNAL_ERROR;
679  }
680 
681  priv->automataState = STATE_SUSPENDED;
682  return CHANNEL_RC_OK;
683 }
684 
690 UINT rdpei_server_resume(RdpeiServerContext* context)
691 {
692  ULONG written = 0;
693  RdpeiServerPrivate* priv = context->priv;
694 
695  switch (priv->automataState)
696  {
697  case STATE_WAITING_FRAME:
698  WLog_ERR(TAG, "not suspended");
699  return CHANNEL_RC_OK;
700  case STATE_SUSPENDED:
701  break;
702  default:
703  WLog_ERR(TAG, "called from unexpected state %d", priv->automataState);
704  return ERROR_INVALID_STATE;
705  }
706 
707  Stream_SetPosition(priv->outputStream, 0);
708  if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH))
709  {
710  WLog_ERR(TAG, "Stream_EnsureCapacity failed!");
711  return CHANNEL_RC_NO_MEMORY;
712  }
713 
714  Stream_Write_UINT16(priv->outputStream, EVENTID_RESUME_TOUCH);
715  Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH);
716 
717  const size_t pos = Stream_GetPosition(priv->outputStream);
718 
719  WINPR_ASSERT(pos <= UINT32_MAX);
720  if (!WTSVirtualChannelWrite(priv->channelHandle, Stream_BufferAs(priv->outputStream, char),
721  (ULONG)pos, &written))
722  {
723  WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
724  return ERROR_INTERNAL_ERROR;
725  }
726 
727  priv->automataState = STATE_WAITING_FRAME;
728  return CHANNEL_RC_OK;
729 }
a touch event with some frames
a touch event with some frames
a frame containing contact points