summaryrefslogtreecommitdiffstats
path: root/channels
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
commita9bcc81f821d7c66f623779fa5147e728eb3c388 (patch)
tree98676963bcdd537ae5908a067a8eb110b93486a6 /channels
parentInitial commit. (diff)
downloadfreerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz
freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--channels/CMakeLists.txt292
-rw-r--r--channels/ainput/CMakeLists.txt27
-rw-r--r--channels/ainput/ChannelOptions.cmake13
-rw-r--r--channels/ainput/client/CMakeLists.txt32
-rw-r--r--channels/ainput/client/ainput_main.c183
-rw-r--r--channels/ainput/client/ainput_main.h40
-rw-r--r--channels/ainput/common/ainput_common.h59
-rw-r--r--channels/ainput/server/CMakeLists.txt28
-rw-r--r--channels/ainput/server/ainput_main.c597
-rw-r--r--channels/audin/CMakeLists.txt26
-rw-r--r--channels/audin/ChannelOptions.cmake17
-rw-r--r--channels/audin/client/CMakeLists.txt63
-rw-r--r--channels/audin/client/alsa/CMakeLists.txt35
-rw-r--r--channels/audin/client/alsa/audin_alsa.c453
-rw-r--r--channels/audin/client/audin_main.c1109
-rw-r--r--channels/audin/client/audin_main.h33
-rw-r--r--channels/audin/client/ios/CMakeLists.txt39
-rw-r--r--channels/audin/client/ios/audin_ios.m335
-rw-r--r--channels/audin/client/mac/CMakeLists.txt41
-rw-r--r--channels/audin/client/mac/audin_mac.m463
-rw-r--r--channels/audin/client/opensles/CMakeLists.txt36
-rw-r--r--channels/audin/client/opensles/audin_opensl_es.c336
-rw-r--r--channels/audin/client/opensles/opensl_io.c388
-rw-r--r--channels/audin/client/opensles/opensl_io.h65
-rw-r--r--channels/audin/client/oss/CMakeLists.txt36
-rw-r--r--channels/audin/client/oss/audin_oss.c491
-rw-r--r--channels/audin/client/pulse/CMakeLists.txt35
-rw-r--r--channels/audin/client/pulse/audin_pulse.c566
-rw-r--r--channels/audin/client/sndio/CMakeLists.txt36
-rw-r--r--channels/audin/client/sndio/audin_sndio.c352
-rw-r--r--channels/audin/client/winmm/CMakeLists.txt31
-rw-r--r--channels/audin/client/winmm/audin_winmm.c566
-rw-r--r--channels/audin/server/CMakeLists.txt27
-rw-r--r--channels/audin/server/audin.c914
-rw-r--r--channels/client/CMakeLists.txt125
-rw-r--r--channels/client/addin.c761
-rw-r--r--channels/client/addin.h28
-rw-r--r--channels/client/generic_dynvc.c212
-rw-r--r--channels/client/tables.c.in33
-rw-r--r--channels/client/tables.h54
-rw-r--r--channels/cliprdr/CMakeLists.txt26
-rw-r--r--channels/cliprdr/ChannelOptions.cmake13
-rw-r--r--channels/cliprdr/client/CMakeLists.txt32
-rw-r--r--channels/cliprdr/client/cliprdr_format.c243
-rw-r--r--channels/cliprdr/client/cliprdr_format.h37
-rw-r--r--channels/cliprdr/client/cliprdr_main.c1192
-rw-r--r--channels/cliprdr/client/cliprdr_main.h61
-rw-r--r--channels/cliprdr/cliprdr_common.c566
-rw-r--r--channels/cliprdr/cliprdr_common.h61
-rw-r--r--channels/cliprdr/server/CMakeLists.txt30
-rw-r--r--channels/cliprdr/server/cliprdr_main.c1528
-rw-r--r--channels/cliprdr/server/cliprdr_main.h47
-rw-r--r--channels/disp/CMakeLists.txt26
-rw-r--r--channels/disp/ChannelOptions.cmake12
-rw-r--r--channels/disp/client/CMakeLists.txt32
-rw-r--r--channels/disp/client/disp_main.c323
-rw-r--r--channels/disp/client/disp_main.h36
-rw-r--r--channels/disp/disp_common.c55
-rw-r--r--channels/disp/disp_common.h32
-rw-r--r--channels/disp/server/CMakeLists.txt32
-rw-r--r--channels/disp/server/disp_main.c632
-rw-r--r--channels/disp/server/disp_main.h37
-rw-r--r--channels/drdynvc/CMakeLists.txt26
-rw-r--r--channels/drdynvc/ChannelOptions.cmake13
-rw-r--r--channels/drdynvc/client/CMakeLists.txt25
-rw-r--r--channels/drdynvc/client/drdynvc_main.c2046
-rw-r--r--channels/drdynvc/client/drdynvc_main.h133
-rw-r--r--channels/drdynvc/server/CMakeLists.txt28
-rw-r--r--channels/drdynvc/server/drdynvc_main.c204
-rw-r--r--channels/drdynvc/server/drdynvc_main.h37
-rw-r--r--channels/drive/CMakeLists.txt23
-rw-r--r--channels/drive/ChannelOptions.cmake13
-rw-r--r--channels/drive/client/CMakeLists.txt29
-rw-r--r--channels/drive/client/drive_file.c971
-rw-r--r--channels/drive/client/drive_file.h67
-rw-r--r--channels/drive/client/drive_main.c1120
-rw-r--r--channels/echo/CMakeLists.txt26
-rw-r--r--channels/echo/ChannelOptions.cmake13
-rw-r--r--channels/echo/client/CMakeLists.txt30
-rw-r--r--channels/echo/client/echo_main.c92
-rw-r--r--channels/echo/client/echo_main.h40
-rw-r--r--channels/echo/server/CMakeLists.txt27
-rw-r--r--channels/echo/server/echo_main.c381
-rw-r--r--channels/encomsp/CMakeLists.txt26
-rw-r--r--channels/encomsp/ChannelOptions.cmake13
-rw-r--r--channels/encomsp/client/CMakeLists.txt27
-rw-r--r--channels/encomsp/client/encomsp_main.c1304
-rw-r--r--channels/encomsp/client/encomsp_main.h42
-rw-r--r--channels/encomsp/server/CMakeLists.txt30
-rw-r--r--channels/encomsp/server/encomsp_main.c372
-rw-r--r--channels/encomsp/server/encomsp_main.h36
-rw-r--r--channels/geometry/CMakeLists.txt22
-rw-r--r--channels/geometry/ChannelOptions.cmake11
-rw-r--r--channels/geometry/client/CMakeLists.txt31
-rw-r--r--channels/geometry/client/geometry_main.c402
-rw-r--r--channels/geometry/client/geometry_main.h30
-rw-r--r--channels/gfxredir/CMakeLists.txt23
-rw-r--r--channels/gfxredir/ChannelOptions.cmake12
-rw-r--r--channels/gfxredir/gfxredir_common.c55
-rw-r--r--channels/gfxredir/gfxredir_common.h32
-rw-r--r--channels/gfxredir/server/CMakeLists.txt32
-rw-r--r--channels/gfxredir/server/gfxredir_main.c794
-rw-r--r--channels/gfxredir/server/gfxredir_main.h37
-rw-r--r--channels/location/CMakeLists.txt22
-rw-r--r--channels/location/ChannelOptions.cmake12
-rw-r--r--channels/location/server/CMakeLists.txt28
-rw-r--r--channels/location/server/location_main.c634
-rw-r--r--channels/parallel/CMakeLists.txt22
-rw-r--r--channels/parallel/ChannelOptions.cmake23
-rw-r--r--channels/parallel/client/CMakeLists.txt27
-rw-r--r--channels/parallel/client/parallel_main.c503
-rw-r--r--channels/printer/CMakeLists.txt22
-rw-r--r--channels/printer/ChannelOptions.cmake33
-rw-r--r--channels/printer/client/CMakeLists.txt35
-rw-r--r--channels/printer/client/cups/CMakeLists.txt33
-rw-r--r--channels/printer/client/cups/printer_cups.c462
-rw-r--r--channels/printer/client/printer_main.c1159
-rw-r--r--channels/printer/client/win/CMakeLists.txt30
-rw-r--r--channels/printer/client/win/printer_win.c463
-rw-r--r--channels/printer/printer.h36
-rw-r--r--channels/rail/CMakeLists.txt26
-rw-r--r--channels/rail/ChannelOptions.cmake13
-rw-r--r--channels/rail/client/CMakeLists.txt33
-rw-r--r--channels/rail/client/client_rails.c98
-rw-r--r--channels/rail/client/rail_main.c756
-rw-r--r--channels/rail/client/rail_main.h60
-rw-r--r--channels/rail/client/rail_orders.c1564
-rw-r--r--channels/rail/client/rail_orders.h60
-rw-r--r--channels/rail/rail_common.c591
-rw-r--r--channels/rail/rail_common.h76
-rw-r--r--channels/rail/server/CMakeLists.txt33
-rw-r--r--channels/rail/server/rail_main.c1728
-rw-r--r--channels/rail/server/rail_main.h44
-rw-r--r--channels/rdp2tcp/CMakeLists.txt22
-rw-r--r--channels/rdp2tcp/ChannelOptions.cmake10
-rw-r--r--channels/rdp2tcp/client/CMakeLists.txt27
-rw-r--r--channels/rdp2tcp/client/rdp2tcp_main.c357
-rw-r--r--channels/rdpdr/CMakeLists.txt26
-rw-r--r--channels/rdpdr/ChannelOptions.cmake13
-rw-r--r--channels/rdpdr/client/CMakeLists.txt42
-rw-r--r--channels/rdpdr/client/devman.c236
-rw-r--r--channels/rdpdr/client/devman.h36
-rw-r--r--channels/rdpdr/client/irp.c165
-rw-r--r--channels/rdpdr/client/irp.h29
-rw-r--r--channels/rdpdr/client/rdpdr_capabilities.c259
-rw-r--r--channels/rdpdr/client/rdpdr_capabilities.h31
-rw-r--r--channels/rdpdr/client/rdpdr_main.c2342
-rw-r--r--channels/rdpdr/client/rdpdr_main.h121
-rw-r--r--channels/rdpdr/server/CMakeLists.txt29
-rw-r--r--channels/rdpdr/server/rdpdr_main.c3574
-rw-r--r--channels/rdpdr/server/rdpdr_main.h47
-rw-r--r--channels/rdpecam/CMakeLists.txt22
-rw-r--r--channels/rdpecam/ChannelOptions.cmake12
-rw-r--r--channels/rdpecam/server/CMakeLists.txt29
-rw-r--r--channels/rdpecam/server/camera_device_enumerator_main.c611
-rw-r--r--channels/rdpecam/server/camera_device_main.c966
-rw-r--r--channels/rdpei/CMakeLists.txt26
-rw-r--r--channels/rdpei/ChannelOptions.cmake13
-rw-r--r--channels/rdpei/client/CMakeLists.txt32
-rw-r--r--channels/rdpei/client/rdpei_main.c1467
-rw-r--r--channels/rdpei/client/rdpei_main.h89
-rw-r--r--channels/rdpei/rdpei_common.c641
-rw-r--r--channels/rdpei/rdpei_common.h57
-rw-r--r--channels/rdpei/server/CMakeLists.txt32
-rw-r--r--channels/rdpei/server/rdpei_main.c720
-rw-r--r--channels/rdpei/server/rdpei_main.h32
-rw-r--r--channels/rdpemsc/CMakeLists.txt22
-rw-r--r--channels/rdpemsc/ChannelOptions.cmake12
-rw-r--r--channels/rdpemsc/server/CMakeLists.txt28
-rw-r--r--channels/rdpemsc/server/mouse_cursor_main.c727
-rw-r--r--channels/rdpgfx/CMakeLists.txt26
-rw-r--r--channels/rdpgfx/ChannelOptions.cmake13
-rw-r--r--channels/rdpgfx/client/CMakeLists.txt35
-rw-r--r--channels/rdpgfx/client/rdpgfx_codec.c294
-rw-r--r--channels/rdpgfx/client/rdpgfx_codec.h35
-rw-r--r--channels/rdpgfx/client/rdpgfx_main.c2417
-rw-r--r--channels/rdpgfx/client/rdpgfx_main.h61
-rw-r--r--channels/rdpgfx/rdpgfx_common.c197
-rw-r--r--channels/rdpgfx/rdpgfx_common.h54
-rw-r--r--channels/rdpgfx/server/CMakeLists.txt33
-rw-r--r--channels/rdpgfx/server/rdpgfx_main.c1863
-rw-r--r--channels/rdpgfx/server/rdpgfx_main.h42
-rw-r--r--channels/rdpsnd/CMakeLists.txt29
-rw-r--r--channels/rdpsnd/ChannelOptions.cmake13
-rw-r--r--channels/rdpsnd/client/CMakeLists.txt66
-rw-r--r--channels/rdpsnd/client/alsa/CMakeLists.txt34
-rw-r--r--channels/rdpsnd/client/alsa/rdpsnd_alsa.c574
-rw-r--r--channels/rdpsnd/client/fake/CMakeLists.txt32
-rw-r--r--channels/rdpsnd/client/fake/rdpsnd_fake.c147
-rw-r--r--channels/rdpsnd/client/ios/CMakeLists.txt40
-rw-r--r--channels/rdpsnd/client/ios/TPCircularBuffer.c153
-rw-r--r--channels/rdpsnd/client/ios/TPCircularBuffer.h217
-rw-r--r--channels/rdpsnd/client/ios/rdpsnd_ios.c282
-rw-r--r--channels/rdpsnd/client/mac/CMakeLists.txt44
-rw-r--r--channels/rdpsnd/client/mac/rdpsnd_mac.m402
-rw-r--r--channels/rdpsnd/client/opensles/CMakeLists.txt35
-rw-r--r--channels/rdpsnd/client/opensles/opensl_io.c422
-rw-r--r--channels/rdpsnd/client/opensles/opensl_io.h110
-rw-r--r--channels/rdpsnd/client/opensles/rdpsnd_opensles.c378
-rw-r--r--channels/rdpsnd/client/oss/CMakeLists.txt35
-rw-r--r--channels/rdpsnd/client/oss/rdpsnd_oss.c478
-rw-r--r--channels/rdpsnd/client/pulse/CMakeLists.txt36
-rw-r--r--channels/rdpsnd/client/pulse/rdpsnd_pulse.c768
-rw-r--r--channels/rdpsnd/client/rdpsnd_main.c1850
-rw-r--r--channels/rdpsnd/client/rdpsnd_main.h41
-rw-r--r--channels/rdpsnd/client/sndio/CMakeLists.txt36
-rw-r--r--channels/rdpsnd/client/sndio/rdpsnd_sndio.c217
-rw-r--r--channels/rdpsnd/client/winmm/CMakeLists.txt32
-rw-r--r--channels/rdpsnd/client/winmm/rdpsnd_winmm.c347
-rw-r--r--channels/rdpsnd/common/CMakeLists.txt27
-rw-r--r--channels/rdpsnd/common/rdpsnd_common.c21
-rw-r--r--channels/rdpsnd/common/rdpsnd_common.h43
-rw-r--r--channels/rdpsnd/server/CMakeLists.txt29
-rw-r--r--channels/rdpsnd/server/rdpsnd_main.c1231
-rw-r--r--channels/rdpsnd/server/rdpsnd_main.h59
-rw-r--r--channels/remdesk/CMakeLists.txt26
-rw-r--r--channels/remdesk/ChannelOptions.cmake12
-rw-r--r--channels/remdesk/client/CMakeLists.txt29
-rw-r--r--channels/remdesk/client/remdesk_main.c1108
-rw-r--r--channels/remdesk/client/remdesk_main.h61
-rw-r--r--channels/remdesk/server/CMakeLists.txt29
-rw-r--r--channels/remdesk/server/remdesk_main.c736
-rw-r--r--channels/remdesk/server/remdesk_main.h41
-rw-r--r--channels/serial/CMakeLists.txt23
-rw-r--r--channels/serial/ChannelOptions.cmake23
-rw-r--r--channels/serial/client/CMakeLists.txt27
-rw-r--r--channels/serial/client/serial_main.c978
-rw-r--r--channels/server/CMakeLists.txt43
-rw-r--r--channels/server/channels.c172
-rw-r--r--channels/server/channels.h24
-rw-r--r--channels/smartcard/CMakeLists.txt22
-rw-r--r--channels/smartcard/ChannelOptions.cmake12
-rw-r--r--channels/smartcard/client/CMakeLists.txt33
-rw-r--r--channels/smartcard/client/smartcard_main.c720
-rw-r--r--channels/smartcard/client/smartcard_main.h59
-rw-r--r--channels/sshagent/CMakeLists.txt23
-rw-r--r--channels/sshagent/ChannelOptions.cmake12
-rw-r--r--channels/sshagent/client/CMakeLists.txt32
-rw-r--r--channels/sshagent/client/sshagent_main.c375
-rw-r--r--channels/sshagent/client/sshagent_main.h42
-rw-r--r--channels/telemetry/CMakeLists.txt22
-rw-r--r--channels/telemetry/ChannelOptions.cmake12
-rw-r--r--channels/telemetry/server/CMakeLists.txt28
-rw-r--r--channels/telemetry/server/telemetry_main.c442
-rw-r--r--channels/tsmf/CMakeLists.txt22
-rw-r--r--channels/tsmf/ChannelOptions.cmake23
-rw-r--r--channels/tsmf/client/CMakeLists.txt84
-rw-r--r--channels/tsmf/client/alsa/CMakeLists.txt35
-rw-r--r--channels/tsmf/client/alsa/tsmf_alsa.c240
-rw-r--r--channels/tsmf/client/ffmpeg/CMakeLists.txt42
-rw-r--r--channels/tsmf/client/ffmpeg/tsmf_ffmpeg.c695
-rw-r--r--channels/tsmf/client/gstreamer/CMakeLists.txt76
-rw-r--r--channels/tsmf/client/gstreamer/tsmf_X11.c500
-rw-r--r--channels/tsmf/client/gstreamer/tsmf_gstreamer.c1054
-rw-r--r--channels/tsmf/client/gstreamer/tsmf_platform.h85
-rw-r--r--channels/tsmf/client/oss/CMakeLists.txt35
-rw-r--r--channels/tsmf/client/oss/tsmf_oss.c248
-rw-r--r--channels/tsmf/client/pulse/CMakeLists.txt35
-rw-r--r--channels/tsmf/client/pulse/tsmf_pulse.c414
-rw-r--r--channels/tsmf/client/tsmf_audio.c97
-rw-r--r--channels/tsmf/client/tsmf_audio.h51
-rw-r--r--channels/tsmf/client/tsmf_codec.c614
-rw-r--r--channels/tsmf/client/tsmf_codec.h28
-rw-r--r--channels/tsmf/client/tsmf_constants.h139
-rw-r--r--channels/tsmf/client/tsmf_decoder.c120
-rw-r--r--channels/tsmf/client/tsmf_decoder.h78
-rw-r--r--channels/tsmf/client/tsmf_ifman.c841
-rw-r--r--channels/tsmf/client/tsmf_ifman.h68
-rw-r--r--channels/tsmf/client/tsmf_main.c609
-rw-r--r--channels/tsmf/client/tsmf_main.h65
-rw-r--r--channels/tsmf/client/tsmf_media.c1544
-rw-r--r--channels/tsmf/client/tsmf_media.h72
-rw-r--r--channels/tsmf/client/tsmf_types.h61
-rw-r--r--channels/urbdrc/CMakeLists.txt30
-rw-r--r--channels/urbdrc/ChannelOptions.cmake18
-rw-r--r--channels/urbdrc/client/CMakeLists.txt40
-rw-r--r--channels/urbdrc/client/data_transfer.c1949
-rw-r--r--channels/urbdrc/client/data_transfer.h36
-rw-r--r--channels/urbdrc/client/libusb/CMakeLists.txt36
-rw-r--r--channels/urbdrc/client/libusb/libusb_udevice.c1841
-rw-r--r--channels/urbdrc/client/libusb/libusb_udevice.h76
-rw-r--r--channels/urbdrc/client/libusb/libusb_udevman.c970
-rw-r--r--channels/urbdrc/client/urbdrc_main.c1023
-rw-r--r--channels/urbdrc/client/urbdrc_main.h222
-rw-r--r--channels/urbdrc/common/CMakeLists.txt29
-rw-r--r--channels/urbdrc/common/msusb.c395
-rw-r--r--channels/urbdrc/common/msusb.h98
-rw-r--r--channels/urbdrc/common/urbdrc_helpers.c425
-rw-r--r--channels/urbdrc/common/urbdrc_helpers.h45
-rw-r--r--channels/urbdrc/common/urbdrc_types.h306
-rw-r--r--channels/video/CMakeLists.txt22
-rw-r--r--channels/video/ChannelOptions.cmake12
-rw-r--r--channels/video/client/CMakeLists.txt30
-rw-r--r--channels/video/client/video_main.c1229
-rw-r--r--channels/video/client/video_main.h31
295 files changed, 79537 insertions, 0 deletions
diff --git a/channels/CMakeLists.txt b/channels/CMakeLists.txt
new file mode 100644
index 0000000..4e3fe74
--- /dev/null
+++ b/channels/CMakeLists.txt
@@ -0,0 +1,292 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+include(CMakeParseArguments)
+include(CMakeDependentOption)
+
+macro(define_channel_options)
+ set(PREFIX "CHANNEL")
+
+ cmake_parse_arguments(${PREFIX}
+ ""
+ "NAME;TYPE;DESCRIPTION;SPECIFICATIONS;DEFAULT"
+ ""
+ ${ARGN})
+
+ string(TOUPPER "CHANNEL_${CHANNEL_NAME}" CHANNEL_OPTION)
+ string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT" CHANNEL_CLIENT_OPTION)
+ string(TOUPPER "CHANNEL_${CHANNEL_NAME}_SERVER" CHANNEL_SERVER_OPTION)
+ string(TOUPPER "${CHANNEL_TYPE}" CHANNEL_TYPE)
+
+ if(${${CHANNEL_CLIENT_OPTION}})
+ set(OPTION_CLIENT_DEFAULT ${${CHANNEL_CLIENT_OPTION}})
+ endif()
+
+ if(${${CHANNEL_SERVER_OPTION}})
+ set(OPTION_SERVER_DEFAULT ${${CHANNEL_SERVER_OPTION}})
+ endif()
+
+ if(${${CHANNEL_OPTION}})
+ set(OPTION_DEFAULT ${${CHANNEL_OPTION}})
+ endif()
+
+ if(${OPTION_CLIENT_DEFAULT} OR ${OPTION_SERVER_DEFAULT})
+ set(OPTION_DEFAULT "ON")
+ endif()
+
+ set(CHANNEL_DEFAULT ${OPTION_DEFAULT})
+
+ set(CHANNEL_OPTION_DOC "Build ${CHANNEL_NAME} ${CHANNEL_TYPE} channel")
+
+ if ("${CHANNEL_TYPE}" STREQUAL "DYNAMIC")
+ CMAKE_DEPENDENT_OPTION(${CHANNEL_OPTION} "${CHANNEL_OPTION_DOC}" ${CHANNEL_DEFAULT} "CHANNEL_DRDYNVC" OFF)
+ else()
+ option(${CHANNEL_OPTION} "${CHANNEL_OPTION_DOC}" ${CHANNEL_DEFAULT})
+ endif()
+
+endmacro(define_channel_options)
+
+macro(define_channel_client_options _channel_client_default)
+ string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT" CHANNEL_CLIENT_OPTION)
+ string(TOUPPER "CHANNEL_${CHANNEL_NAME}" CHANNEL_OPTION)
+ set(CHANNEL_CLIENT_OPTION_DOC "Build ${CHANNEL_NAME} ${CHANNEL_TYPE} channel client")
+
+ CMAKE_DEPENDENT_OPTION(${CHANNEL_CLIENT_OPTION} "${CHANNEL_CLIENT_OPTION_DOC}"
+ ${_channel_client_default} "${CHANNEL_OPTION}" OFF)
+endmacro(define_channel_client_options)
+
+macro(define_channel_server_options _channel_server_default)
+ string(TOUPPER "CHANNEL_${CHANNEL_NAME}_SERVER" CHANNEL_SERVER_OPTION)
+ string(TOUPPER "CHANNEL_${CHANNEL_NAME}" CHANNEL_OPTION)
+ set(CHANNEL_SERVER_OPTION_DOC "Build ${CHANNEL_NAME} ${CHANNEL_TYPE} channel server")
+
+ CMAKE_DEPENDENT_OPTION(${CHANNEL_SERVER_OPTION} "${CHANNEL_SERVER_OPTION_DOC}"
+ ${_channel_server_default} "${CHANNEL_OPTION}" OFF)
+endmacro(define_channel_server_options)
+
+macro(define_channel _channel_name)
+ set(CHANNEL_NAME ${_channel_name})
+ set(MODULE_NAME ${CHANNEL_NAME})
+ string(TOUPPER "CHANNEL_${CHANNEL_NAME}" MODULE_PREFIX)
+endmacro(define_channel)
+
+macro(define_channel_client _channel_name)
+ set(CHANNEL_NAME ${_channel_name})
+ set(MODULE_NAME "${CHANNEL_NAME}-client")
+ string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT" MODULE_PREFIX)
+endmacro(define_channel_client)
+
+macro(define_channel_server _channel_name)
+ set(CHANNEL_NAME ${_channel_name})
+ set(MODULE_NAME "${CHANNEL_NAME}-server")
+ string(TOUPPER "CHANNEL_${CHANNEL_NAME}_SERVER" MODULE_PREFIX)
+endmacro(define_channel_server)
+
+macro(define_channel_client_subsystem _channel_name _subsystem _type)
+ set(CHANNEL_NAME ${_channel_name})
+ set(CHANNEL_SUBSYSTEM ${_subsystem})
+ string(LENGTH "${_type}" _type_length)
+ string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT" CHANNEL_PREFIX)
+ if(_type_length GREATER 0)
+ set(SUBSYSTEM_TYPE ${_type})
+ set(MODULE_NAME "${CHANNEL_NAME}-client-${CHANNEL_SUBSYSTEM}-${SUBSYSTEM_TYPE}")
+ string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT_${CHANNEL_SUBSYSTEM}_${SUBSYSTEM_TYPE}" MODULE_PREFIX)
+ else()
+ set(MODULE_NAME "${CHANNEL_NAME}-client-${CHANNEL_SUBSYSTEM}")
+ string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT_${CHANNEL_SUBSYSTEM}" MODULE_PREFIX)
+ endif()
+endmacro(define_channel_client_subsystem)
+
+macro(define_channel_server_subsystem _channel_name _subsystem _type)
+ set(CHANNEL_NAME ${_channel_name})
+ set(CHANNEL_SUBSYSTEM ${_subsystem})
+ set(MODULE_NAME "${CHANNEL_NAME}-server-${CHANNEL_SUBSYSTEM}")
+ string(TOUPPER "CHANNEL_${CHANNEL_NAME}_server_${CHANNEL_SUBSYSTEM}" MODULE_PREFIX)
+endmacro(define_channel_server_subsystem)
+
+macro(add_channel_client _channel_prefix _channel_name)
+ add_subdirectory(client)
+ if(${${_channel_prefix}_CLIENT_STATIC})
+ set(CHANNEL_STATIC_CLIENT_MODULES ${CHANNEL_STATIC_CLIENT_MODULES} ${_channel_prefix} PARENT_SCOPE)
+ set(${_channel_prefix}_CLIENT_NAME ${${_channel_prefix}_CLIENT_NAME} PARENT_SCOPE)
+ set(${_channel_prefix}_CLIENT_CHANNEL ${${_channel_prefix}_CLIENT_CHANNEL} PARENT_SCOPE)
+ set(${_channel_prefix}_CLIENT_ENTRY ${${_channel_prefix}_CLIENT_ENTRY} PARENT_SCOPE)
+ set(CHANNEL_STATIC_CLIENT_ENTRIES ${CHANNEL_STATIC_CLIENT_ENTRIES} ${${_channel_prefix}_CLIENT_ENTRY} PARENT_SCOPE)
+ endif()
+endmacro(add_channel_client)
+
+macro(add_channel_server _channel_prefix _channel_name)
+ add_subdirectory(server)
+ if(${${_channel_prefix}_SERVER_STATIC})
+ set(CHANNEL_STATIC_SERVER_MODULES ${CHANNEL_STATIC_SERVER_MODULES} ${_channel_prefix} PARENT_SCOPE)
+ set(${_channel_prefix}_SERVER_NAME ${${_channel_prefix}_SERVER_NAME} PARENT_SCOPE)
+ set(${_channel_prefix}_SERVER_CHANNEL ${${_channel_prefix}_SERVER_CHANNEL} PARENT_SCOPE)
+ set(${_channel_prefix}_SERVER_ENTRY ${${_channel_prefix}_SERVER_ENTRY} PARENT_SCOPE)
+ set(CHANNEL_STATIC_SERVER_ENTRIES ${CHANNEL_STATIC_SERVER_ENTRIES} ${${_channel_prefix}_SERVER_ENTRY} PARENT_SCOPE)
+ endif()
+endmacro(add_channel_server)
+
+macro(add_channel_client_subsystem _channel_prefix _channel_name _subsystem _type)
+ add_subdirectory(${_subsystem})
+ set(_channel_module_name "${_channel_name}-client")
+ string(LENGTH "${_type}" _type_length)
+ if(_type_length GREATER 0)
+ string(TOUPPER "CHANNEL_${_channel_name}_CLIENT_${_subsystem}_${_type}" _subsystem_prefix)
+ else()
+ string(TOUPPER "CHANNEL_${_channel_name}_CLIENT_${_subsystem}" _subsystem_prefix)
+ endif()
+ if(${${_subsystem_prefix}_STATIC})
+ get_target_property(CHANNEL_SUBSYSTEMS ${_channel_module_name} SUBSYSTEMS)
+ if(_type_length GREATER 0)
+ set(SUBSYSTEMS ${SUBSYSTEMS} "${_subsystem}-${_type}")
+ else()
+ set(SUBSYSTEMS ${SUBSYSTEMS} ${_subsystem})
+ endif()
+ set_target_properties(${_channel_module_name} PROPERTIES SUBSYSTEMS "${SUBSYSTEMS}")
+ endif()
+endmacro(add_channel_client_subsystem)
+
+macro(channel_install _targets _destination _export_target)
+ if (NOT BUILD_SHARED_LIBS)
+ foreach(_target_name IN ITEMS ${_targets})
+ target_include_directories(${_target_name} INTERFACE $<INSTALL_INTERFACE:include>)
+ endforeach()
+ install(TARGETS ${_targets} DESTINATION ${_destination} EXPORT ${_export_target})
+ endif()
+endmacro(channel_install)
+
+macro(server_channel_install _targets _destination)
+ channel_install(${_targets} ${_destination} "FreeRDP-ServerTargets")
+endmacro(server_channel_install)
+
+macro(client_channel_install _targets _destination)
+ channel_install(${_targets} ${_destination} "FreeRDP-ClientTargets")
+endmacro(client_channel_install)
+
+macro(add_channel_client_library _module_prefix _module_name _channel_name _dynamic _entry)
+ set(_lnk_dir ${${_module_prefix}_LINK_DIRS})
+ if (NOT "${_lnk_dir}" STREQUAL "")
+ link_directories(${_lnk_dir})
+ endif()
+
+ set(${_module_prefix}_STATIC ON PARENT_SCOPE)
+ set(${_module_prefix}_NAME ${_module_name} PARENT_SCOPE)
+ set(${_module_prefix}_CHANNEL ${_channel_name} PARENT_SCOPE)
+ set(${_module_prefix}_ENTRY ${_entry} PARENT_SCOPE)
+
+ add_library(${_module_name} OBJECT ${${_module_prefix}_SRCS})
+ set_property(TARGET ${_module_name} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client")
+
+ if (${_module_prefix}_LIBS)
+ target_link_libraries(${_module_name} PUBLIC ${${_module_prefix}_LIBS})
+ endif()
+ client_channel_install(${_module_name} ${FREERDP_ADDIN_PATH})
+endmacro(add_channel_client_library)
+
+macro(add_channel_client_subsystem_library _module_prefix _module_name _channel_name _type _dynamic _entry)
+ set(_lnk_dir ${${_module_prefix}_LINK_DIRS})
+ if (NOT "${_lnk_dir}" STREQUAL "")
+ link_directories(${_lnk_dir})
+ endif()
+
+ set(${_module_prefix}_STATIC ON PARENT_SCOPE)
+ set(${_module_prefix}_NAME ${_module_name} PARENT_SCOPE)
+ set(${_module_prefix}_TYPE ${_type} PARENT_SCOPE)
+
+ add_library(${_module_name} OBJECT ${${_module_prefix}_SRCS})
+ set_property(TARGET ${_module_name} PROPERTY FOLDER "Channels/${_channel_name}/Client/Subsystem/${CHANNEL_SUBSYSTEM}")
+
+ if (${_module_prefix}_LIBS)
+ target_link_libraries(${_module_name} PUBLIC ${${_module_prefix}_LIBS})
+ endif()
+ client_channel_install(${_module_name} ${FREERDP_ADDIN_PATH})
+endmacro(add_channel_client_subsystem_library)
+
+macro(add_channel_server_library _module_prefix _module_name _channel_name _dynamic _entry)
+ set(_lnk_dir ${${_module_prefix}_LINK_DIRS})
+ if (NOT "${_lnk_dir}" STREQUAL "")
+ link_directories(${_lnk_dir})
+ endif()
+
+ set(${_module_prefix}_STATIC ON PARENT_SCOPE)
+ set(${_module_prefix}_NAME ${_module_name} PARENT_SCOPE)
+ set(${_module_prefix}_CHANNEL ${_channel_name} PARENT_SCOPE)
+ set(${_module_prefix}_ENTRY ${_entry} PARENT_SCOPE)
+
+ add_library(${_module_name} OBJECT ${${_module_prefix}_SRCS})
+ set_property(TARGET ${_module_name} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Server")
+
+ if (${_module_prefix}_LIBS)
+ target_link_libraries(${_module_name} PUBLIC ${${_module_prefix}_LIBS})
+ endif()
+ server_channel_install(${_module_name} ${FREERDP_ADDIN_PATH})
+endmacro(add_channel_server_library)
+
+set(FILENAME "ChannelOptions.cmake")
+file(GLOB FILEPATHS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*/${FILENAME}")
+
+# We need special treatement for drdynvc:
+# It needs to be the first entry so that every
+# dynamic channel has the dependent options available.
+set(DRDYNVC_MATCH "")
+
+foreach(FILEPATH ${FILEPATHS})
+ if(${FILEPATH} MATCHES "^([^/]*)drdynvc/+${FILENAME}")
+ set(DRDYNVC_MATCH ${FILEPATH})
+ endif()
+endforeach()
+
+if (NOT "${DRDYNVC_MATCH}" STREQUAL "")
+ list(REMOVE_ITEM FILEPATHS ${DRDYNVC_MATCH})
+ list(APPEND FILEPATHS ${DRDYNVC_MATCH})
+ list(REVERSE FILEPATHS) # list PREPEND is not available on old CMake3
+endif()
+
+foreach(FILEPATH ${FILEPATHS})
+ if(${FILEPATH} MATCHES "^([^/]*)/+${FILENAME}")
+ string(REGEX REPLACE "^([^/]*)/+${FILENAME}" "\\1" DIR ${FILEPATH})
+ set(CHANNEL_OPTION)
+ include(${FILEPATH})
+ if(${CHANNEL_OPTION})
+ set(CHANNEL_MESSAGE "Adding ${CHANNEL_TYPE} channel")
+ if(${CHANNEL_CLIENT_OPTION})
+ set(CHANNEL_MESSAGE "${CHANNEL_MESSAGE} client")
+ endif()
+ if(${CHANNEL_SERVER_OPTION})
+ set(CHANNEL_MESSAGE "${CHANNEL_MESSAGE} server")
+ endif()
+ set(CHANNEL_MESSAGE "${CHANNEL_MESSAGE} \"${CHANNEL_NAME}\"")
+ set(CHANNEL_MESSAGE "${CHANNEL_MESSAGE}: ${CHANNEL_DESCRIPTION}")
+ message(STATUS "${CHANNEL_MESSAGE}")
+ add_subdirectory(${DIR})
+ endif()
+ endif()
+endforeach(FILEPATH)
+
+if (WITH_CHANNELS)
+ if(WITH_CLIENT_CHANNELS)
+ add_subdirectory(client)
+ set(FREERDP_CHANNELS_CLIENT_SRCS ${FREERDP_CHANNELS_CLIENT_SRCS} PARENT_SCOPE)
+ set(FREERDP_CHANNELS_CLIENT_LIBS ${FREERDP_CHANNELS_CLIENT_LIBS} PARENT_SCOPE)
+ endif()
+
+ if(WITH_SERVER_CHANNELS)
+ add_subdirectory(server)
+ set(FREERDP_CHANNELS_SERVER_SRCS ${FREERDP_CHANNELS_SERVER_SRCS} PARENT_SCOPE)
+ set(FREERDP_CHANNELS_SERVER_LIBS ${FREERDP_CHANNELS_SERVER_LIBS} PARENT_SCOPE)
+ endif()
+endif()
diff --git a/channels/ainput/CMakeLists.txt b/channels/ainput/CMakeLists.txt
new file mode 100644
index 0000000..b1d1a6a
--- /dev/null
+++ b/channels/ainput/CMakeLists.txt
@@ -0,0 +1,27 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2022 Armin Novak <anovak@thincast.com>
+# Copyright 2022 Thincast Technologies GmbH
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("ainput")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/ainput/ChannelOptions.cmake b/channels/ainput/ChannelOptions.cmake
new file mode 100644
index 0000000..eafc6c0
--- /dev/null
+++ b/channels/ainput/ChannelOptions.cmake
@@ -0,0 +1,13 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT ON)
+
+define_channel_options(NAME "ainput" TYPE "dynamic"
+ DESCRIPTION "Advanced Input Virtual Channel Extension"
+ SPECIFICATIONS "[XXXXX]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/ainput/client/CMakeLists.txt b/channels/ainput/client/CMakeLists.txt
new file mode 100644
index 0000000..00c8e4a
--- /dev/null
+++ b/channels/ainput/client/CMakeLists.txt
@@ -0,0 +1,32 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2022 Armin Novak <anovak@thincast.com>
+# Copyright 2022 Thincast Technologies GmbH
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("ainput")
+
+set(${MODULE_PREFIX}_SRCS
+ ainput_main.c
+ ainput_main.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+)
+
+include_directories(..)
+
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
diff --git a/channels/ainput/client/ainput_main.c b/channels/ainput/client/ainput_main.c
new file mode 100644
index 0000000..1a2128d
--- /dev/null
+++ b/channels/ainput/client/ainput_main.c
@@ -0,0 +1,183 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Advanced Input Virtual Channel Extension
+ *
+ * Copyright 2022 Armin Novak <anovak@thincast.com>
+ * Copyright 2022 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/stream.h>
+#include <winpr/sysinfo.h>
+
+#include "ainput_main.h"
+#include <freerdp/channels/log.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/client/ainput.h>
+#include <freerdp/channels/ainput.h>
+
+#include "../common/ainput_common.h"
+
+#define TAG CHANNELS_TAG("ainput.client")
+
+typedef struct AINPUT_PLUGIN_ AINPUT_PLUGIN;
+struct AINPUT_PLUGIN_
+{
+ GENERIC_DYNVC_PLUGIN base;
+ AInputClientContext* context;
+ UINT32 MajorVersion;
+ UINT32 MinorVersion;
+};
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT ainput_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
+{
+ UINT16 type = 0;
+ AINPUT_PLUGIN* ainput = NULL;
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+
+ WINPR_ASSERT(callback);
+ WINPR_ASSERT(data);
+
+ ainput = (AINPUT_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(ainput);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, data, 2))
+ return ERROR_NO_DATA;
+ Stream_Read_UINT16(data, type);
+ switch (type)
+ {
+ case MSG_AINPUT_VERSION:
+ if (!Stream_CheckAndLogRequiredLength(TAG, data, 8))
+ return ERROR_NO_DATA;
+ Stream_Read_UINT32(data, ainput->MajorVersion);
+ Stream_Read_UINT32(data, ainput->MinorVersion);
+ break;
+ default:
+ WLog_WARN(TAG, "Received unsupported message type 0x%04" PRIx16, type);
+ break;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT ainput_send_input_event(AInputClientContext* context, UINT64 flags, INT32 x, INT32 y)
+{
+ AINPUT_PLUGIN* ainput = NULL;
+ GENERIC_CHANNEL_CALLBACK* callback = NULL;
+ BYTE buffer[32] = { 0 };
+ UINT64 time = 0;
+ wStream sbuffer = { 0 };
+ wStream* s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer));
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(context);
+
+ time = GetTickCount64();
+ ainput = (AINPUT_PLUGIN*)context->handle;
+ WINPR_ASSERT(ainput);
+
+ if (ainput->MajorVersion != AINPUT_VERSION_MAJOR)
+ {
+ WLog_WARN(TAG, "Unsupported channel version %" PRIu32 ".%" PRIu32 ", aborting.",
+ ainput->MajorVersion, ainput->MinorVersion);
+ return CHANNEL_RC_UNSUPPORTED_VERSION;
+ }
+ callback = ainput->base.listener_callback->channel_callback;
+ WINPR_ASSERT(callback);
+
+ {
+ char ebuffer[128] = { 0 };
+ WLog_VRB(TAG, "sending timestamp=0x%08" PRIx64 ", flags=%s, %" PRId32 "x%" PRId32, time,
+ ainput_flags_to_string(flags, ebuffer, sizeof(ebuffer)), x, y);
+ }
+
+ /* Message type */
+ Stream_Write_UINT16(s, MSG_AINPUT_MOUSE);
+
+ /* Event data */
+ Stream_Write_UINT64(s, time);
+ Stream_Write_UINT64(s, flags);
+ Stream_Write_INT32(s, x);
+ Stream_Write_INT32(s, y);
+ Stream_SealLength(s);
+
+ /* ainput back what we have received. AINPUT does not have any message IDs. */
+ WINPR_ASSERT(callback->channel);
+ WINPR_ASSERT(callback->channel->Write);
+ return callback->channel->Write(callback->channel, (ULONG)Stream_Length(s), Stream_Buffer(s),
+ NULL);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT ainput_on_close(IWTSVirtualChannelCallback* pChannelCallback)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+
+ free(callback);
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT init_plugin_cb(GENERIC_DYNVC_PLUGIN* base, rdpContext* rcontext, rdpSettings* settings)
+{
+ AINPUT_PLUGIN* ainput = (AINPUT_PLUGIN*)base;
+ AInputClientContext* context = (AInputClientContext*)calloc(1, sizeof(AInputClientContext));
+ if (!context)
+ return CHANNEL_RC_NO_MEMORY;
+
+ context->handle = (void*)base;
+ context->AInputSendInputEvent = ainput_send_input_event;
+
+ ainput->context = context;
+ ainput->base.iface.pInterface = context;
+ return CHANNEL_RC_OK;
+}
+
+static void terminate_plugin_cb(GENERIC_DYNVC_PLUGIN* base)
+{
+ AINPUT_PLUGIN* ainput = (AINPUT_PLUGIN*)base;
+ free(ainput->context);
+}
+
+static const IWTSVirtualChannelCallback ainput_functions = { ainput_on_data_received,
+ NULL, /* Open */
+ ainput_on_close, NULL };
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT ainput_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
+{
+ return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, AINPUT_DVC_CHANNEL_NAME,
+ sizeof(AINPUT_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK),
+ &ainput_functions, init_plugin_cb, terminate_plugin_cb);
+}
diff --git a/channels/ainput/client/ainput_main.h b/channels/ainput/client/ainput_main.h
new file mode 100644
index 0000000..5e1d5b1
--- /dev/null
+++ b/channels/ainput/client/ainput_main.h
@@ -0,0 +1,40 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Advanced Input Virtual Channel Extension
+ *
+ * Copyright 2022 Armin Novak <anovak@thincast.com>
+ * Copyright 2022 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_AINPUT_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_AINPUT_CLIENT_MAIN_H
+
+#include <freerdp/config.h>
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/log.h>
+
+#define DVC_TAG CHANNELS_TAG("ainput.client")
+#ifdef WITH_DEBUG_DVC
+#define DEBUG_DVC(...) WLog_DBG(DVC_TAG, __VA_ARGS__)
+#else
+#define DEBUG_DVC(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#endif /* FREERDP_CHANNEL_AINPUT_CLIENT_MAIN_H */
diff --git a/channels/ainput/common/ainput_common.h b/channels/ainput/common/ainput_common.h
new file mode 100644
index 0000000..34442f7
--- /dev/null
+++ b/channels/ainput/common/ainput_common.h
@@ -0,0 +1,59 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel
+ *
+ * Copyright 2022 Armin Novak <anovak@thincast.com>
+ * Copyright 2022 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_INT_AINPUT_COMMON_H
+#define FREERDP_INT_AINPUT_COMMON_H
+
+#include <winpr/string.h>
+
+#include <freerdp/channels/ainput.h>
+
+static INLINE const char* ainput_flags_to_string(UINT64 flags, char* buffer, size_t size)
+{
+ char number[32] = { 0 };
+
+ if (flags & AINPUT_FLAGS_HAVE_REL)
+ winpr_str_append("AINPUT_FLAGS_HAVE_REL", buffer, size, "|");
+ if (flags & AINPUT_FLAGS_WHEEL)
+ winpr_str_append("AINPUT_FLAGS_WHEEL", buffer, size, "|");
+ if (flags & AINPUT_FLAGS_MOVE)
+ winpr_str_append("AINPUT_FLAGS_MOVE", buffer, size, "|");
+ if (flags & AINPUT_FLAGS_DOWN)
+ winpr_str_append("AINPUT_FLAGS_DOWN", buffer, size, "|");
+ if (flags & AINPUT_FLAGS_REL)
+ winpr_str_append("AINPUT_FLAGS_REL", buffer, size, "|");
+ if (flags & AINPUT_FLAGS_BUTTON1)
+ winpr_str_append("AINPUT_FLAGS_BUTTON1", buffer, size, "|");
+ if (flags & AINPUT_FLAGS_BUTTON2)
+ winpr_str_append("AINPUT_FLAGS_BUTTON2", buffer, size, "|");
+ if (flags & AINPUT_FLAGS_BUTTON3)
+ winpr_str_append("AINPUT_FLAGS_BUTTON3", buffer, size, "|");
+ if (flags & AINPUT_XFLAGS_BUTTON1)
+ winpr_str_append("AINPUT_XFLAGS_BUTTON1", buffer, size, "|");
+ if (flags & AINPUT_XFLAGS_BUTTON2)
+ winpr_str_append("AINPUT_XFLAGS_BUTTON2", buffer, size, "|");
+
+ _snprintf(number, sizeof(number), "[0x%08" PRIx64 "]", flags);
+ winpr_str_append(number, buffer, size, " ");
+
+ return buffer;
+}
+
+#endif /* FREERDP_INT_AINPUT_COMMON_H */
diff --git a/channels/ainput/server/CMakeLists.txt b/channels/ainput/server/CMakeLists.txt
new file mode 100644
index 0000000..13bf2e4
--- /dev/null
+++ b/channels/ainput/server/CMakeLists.txt
@@ -0,0 +1,28 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2022 Armin Novak <anovak@thincast.com>
+# Copyright 2022 Thincast Technologies GmbH
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_server("ainput")
+
+set(${MODULE_PREFIX}_SRCS
+ ainput_main.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry")
diff --git a/channels/ainput/server/ainput_main.c b/channels/ainput/server/ainput_main.c
new file mode 100644
index 0000000..6bb4650
--- /dev/null
+++ b/channels/ainput/server/ainput_main.c
@@ -0,0 +1,597 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Advanced Input Virtual Channel Extension
+ *
+ * Copyright 2022 Armin Novak <anovak@thincast.com>
+ * Copyright 2022 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/sysinfo.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/ainput.h>
+#include <freerdp/server/ainput.h>
+#include <freerdp/channels/log.h>
+
+#include "../common/ainput_common.h"
+
+#define TAG CHANNELS_TAG("ainput.server")
+
+typedef enum
+{
+ AINPUT_INITIAL,
+ AINPUT_OPENED,
+ AINPUT_VERSION_SENT,
+} eAInputChannelState;
+
+typedef struct
+{
+ ainput_server_context context;
+
+ BOOL opened;
+
+ HANDLE stopEvent;
+
+ HANDLE thread;
+ void* ainput_channel;
+
+ DWORD SessionId;
+
+ BOOL isOpened;
+ BOOL externalThread;
+
+ /* Channel state */
+ eAInputChannelState state;
+
+ wStream* buffer;
+} ainput_server;
+
+static UINT ainput_server_context_poll(ainput_server_context* context);
+static BOOL ainput_server_context_handle(ainput_server_context* context, HANDLE* handle);
+static UINT ainput_server_context_poll_int(ainput_server_context* context);
+
+static BOOL ainput_server_is_open(ainput_server_context* context)
+{
+ ainput_server* ainput = (ainput_server*)context;
+
+ WINPR_ASSERT(ainput);
+ return ainput->isOpened;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT ainput_server_open_channel(ainput_server* ainput)
+{
+ DWORD Error = 0;
+ HANDLE hEvent = NULL;
+ DWORD StartTick = 0;
+ DWORD BytesReturned = 0;
+ PULONG pSessionId = NULL;
+
+ WINPR_ASSERT(ainput);
+
+ if (WTSQuerySessionInformationA(ainput->context.vcm, WTS_CURRENT_SESSION, WTSSessionId,
+ (LPSTR*)&pSessionId, &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSQuerySessionInformationA failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ ainput->SessionId = (DWORD)*pSessionId;
+ WTSFreeMemory(pSessionId);
+ hEvent = WTSVirtualChannelManagerGetEventHandle(ainput->context.vcm);
+ StartTick = GetTickCount();
+
+ while (ainput->ainput_channel == NULL)
+ {
+ if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED)
+ {
+ Error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error);
+ return Error;
+ }
+
+ ainput->ainput_channel = WTSVirtualChannelOpenEx(ainput->SessionId, AINPUT_DVC_CHANNEL_NAME,
+ WTS_CHANNEL_OPTION_DYNAMIC);
+
+ Error = GetLastError();
+
+ if (Error == ERROR_NOT_FOUND)
+ {
+ WLog_DBG(TAG, "Channel %s not found", AINPUT_DVC_CHANNEL_NAME);
+ break;
+ }
+
+ if (ainput->ainput_channel)
+ {
+ UINT32 channelId = 0;
+ BOOL status = TRUE;
+
+ channelId = WTSChannelGetIdByHandle(ainput->ainput_channel);
+
+ IFCALLRET(ainput->context.ChannelIdAssigned, status, &ainput->context, channelId);
+ if (!status)
+ {
+ WLog_ERR(TAG, "context->ChannelIdAssigned failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ break;
+ }
+
+ if (GetTickCount() - StartTick > 5000)
+ {
+ WLog_WARN(TAG, "Timeout opening channel %s", AINPUT_DVC_CHANNEL_NAME);
+ break;
+ }
+ }
+
+ return ainput->ainput_channel ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR;
+}
+
+static UINT ainput_server_send_version(ainput_server* ainput)
+{
+ ULONG written = 0;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(ainput);
+
+ s = ainput->buffer;
+ WINPR_ASSERT(s);
+
+ Stream_SetPosition(s, 0);
+ if (!Stream_EnsureCapacity(s, 10))
+ {
+ WLog_WARN(TAG, "[%s] out of memory", AINPUT_DVC_CHANNEL_NAME);
+ return ERROR_OUTOFMEMORY;
+ }
+
+ Stream_Write_UINT16(s, MSG_AINPUT_VERSION);
+ Stream_Write_UINT32(s, AINPUT_VERSION_MAJOR); /* Version (4 bytes) */
+ Stream_Write_UINT32(s, AINPUT_VERSION_MINOR); /* Version (4 bytes) */
+
+ WINPR_ASSERT(Stream_GetPosition(s) <= ULONG_MAX);
+ if (!WTSVirtualChannelWrite(ainput->ainput_channel, (PCHAR)Stream_Buffer(s),
+ (ULONG)Stream_GetPosition(s), &written))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT ainput_server_recv_mouse_event(ainput_server* ainput, wStream* s)
+{
+ UINT error = CHANNEL_RC_OK;
+ UINT64 flags = 0;
+ UINT64 time = 0;
+ INT32 x = 0;
+ INT32 y = 0;
+ char buffer[128] = { 0 };
+
+ WINPR_ASSERT(ainput);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 24))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT64(s, time);
+ Stream_Read_UINT64(s, flags);
+ Stream_Read_INT32(s, x);
+ Stream_Read_INT32(s, y);
+
+ WLog_VRB(TAG, "received: time=0x%08" PRIx64 ", flags=%s, %" PRId32 "x%" PRId32, time,
+ ainput_flags_to_string(flags, buffer, sizeof(buffer)), x, y);
+ IFCALLRET(ainput->context.MouseEvent, error, &ainput->context, time, flags, x, y);
+
+ return error;
+}
+
+static HANDLE ainput_server_get_channel_handle(ainput_server* ainput)
+{
+ void* buffer = NULL;
+ DWORD BytesReturned = 0;
+ HANDLE ChannelEvent = NULL;
+
+ WINPR_ASSERT(ainput);
+
+ if (WTSVirtualChannelQuery(ainput->ainput_channel, WTSVirtualEventHandle, &buffer,
+ &BytesReturned) == TRUE)
+ {
+ if (BytesReturned == sizeof(HANDLE))
+ CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE));
+
+ WTSFreeMemory(buffer);
+ }
+
+ return ChannelEvent;
+}
+
+static DWORD WINAPI ainput_server_thread_func(LPVOID arg)
+{
+ DWORD nCount = 0;
+ HANDLE events[2] = { 0 };
+ ainput_server* ainput = (ainput_server*)arg;
+ UINT error = CHANNEL_RC_OK;
+ DWORD status = 0;
+
+ WINPR_ASSERT(ainput);
+
+ nCount = 0;
+ events[nCount++] = ainput->stopEvent;
+
+ while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0))
+ {
+ switch (ainput->state)
+ {
+ case AINPUT_OPENED:
+ events[1] = ainput_server_get_channel_handle(ainput);
+ nCount = 2;
+ status = WaitForMultipleObjects(nCount, events, FALSE, 100);
+ switch (status)
+ {
+ case WAIT_TIMEOUT:
+ case WAIT_OBJECT_0 + 1:
+ case WAIT_OBJECT_0:
+ error = ainput_server_context_poll_int(&ainput->context);
+ break;
+ case WAIT_FAILED:
+ default:
+ WLog_WARN(TAG, "[%s] Wait for open failed", AINPUT_DVC_CHANNEL_NAME);
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+ break;
+ case AINPUT_VERSION_SENT:
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+ switch (status)
+ {
+ case WAIT_TIMEOUT:
+ case WAIT_OBJECT_0 + 1:
+ case WAIT_OBJECT_0:
+ error = ainput_server_context_poll_int(&ainput->context);
+ break;
+
+ case WAIT_FAILED:
+ default:
+ WLog_WARN(TAG, "[%s] Wait for version failed", AINPUT_DVC_CHANNEL_NAME);
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+ break;
+ default:
+ error = ainput_server_context_poll_int(&ainput->context);
+ break;
+ }
+ }
+
+ WTSVirtualChannelClose(ainput->ainput_channel);
+ ainput->ainput_channel = NULL;
+
+ if (error && ainput->context.rdpcontext)
+ setChannelError(ainput->context.rdpcontext, error,
+ "ainput_server_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT ainput_server_open(ainput_server_context* context)
+{
+ ainput_server* ainput = (ainput_server*)context;
+
+ WINPR_ASSERT(ainput);
+
+ if (!ainput->externalThread && (ainput->thread == NULL))
+ {
+ ainput->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!ainput->stopEvent)
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ ainput->thread = CreateThread(NULL, 0, ainput_server_thread_func, ainput, 0, NULL);
+ if (!ainput->thread)
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ CloseHandle(ainput->stopEvent);
+ ainput->stopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+ ainput->isOpened = TRUE;
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT ainput_server_close(ainput_server_context* context)
+{
+ UINT error = CHANNEL_RC_OK;
+ ainput_server* ainput = (ainput_server*)context;
+
+ WINPR_ASSERT(ainput);
+
+ if (!ainput->externalThread && ainput->thread)
+ {
+ SetEvent(ainput->stopEvent);
+
+ if (WaitForSingleObject(ainput->thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ CloseHandle(ainput->thread);
+ CloseHandle(ainput->stopEvent);
+ ainput->thread = NULL;
+ ainput->stopEvent = NULL;
+ }
+ if (ainput->externalThread)
+ {
+ if (ainput->state != AINPUT_INITIAL)
+ {
+ WTSVirtualChannelClose(ainput->ainput_channel);
+ ainput->ainput_channel = NULL;
+ ainput->state = AINPUT_INITIAL;
+ }
+ }
+ ainput->isOpened = FALSE;
+
+ return error;
+}
+
+static UINT ainput_server_initialize(ainput_server_context* context, BOOL externalThread)
+{
+ UINT error = CHANNEL_RC_OK;
+ ainput_server* ainput = (ainput_server*)context;
+
+ WINPR_ASSERT(ainput);
+
+ if (ainput->isOpened)
+ {
+ WLog_WARN(TAG, "Application error: AINPUT channel already initialized, calling in this "
+ "state is not possible!");
+ return ERROR_INVALID_STATE;
+ }
+ ainput->externalThread = externalThread;
+ return error;
+}
+
+ainput_server_context* ainput_server_context_new(HANDLE vcm)
+{
+ ainput_server* ainput = (ainput_server*)calloc(1, sizeof(ainput_server));
+
+ if (!ainput)
+ return NULL;
+
+ ainput->context.vcm = vcm;
+ ainput->context.Open = ainput_server_open;
+ ainput->context.IsOpen = ainput_server_is_open;
+ ainput->context.Close = ainput_server_close;
+ ainput->context.Initialize = ainput_server_initialize;
+ ainput->context.Poll = ainput_server_context_poll;
+ ainput->context.ChannelHandle = ainput_server_context_handle;
+
+ ainput->buffer = Stream_New(NULL, 4096);
+ if (!ainput->buffer)
+ goto fail;
+ return &ainput->context;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ ainput_server_context_free(&ainput->context);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void ainput_server_context_free(ainput_server_context* context)
+{
+ ainput_server* ainput = (ainput_server*)context;
+ if (ainput)
+ {
+ ainput_server_close(context);
+ Stream_Free(ainput->buffer, TRUE);
+ }
+ free(ainput);
+}
+
+static UINT ainput_process_message(ainput_server* ainput)
+{
+ BOOL rc = 0;
+ UINT error = ERROR_INTERNAL_ERROR;
+ ULONG BytesReturned = 0;
+ ULONG ActualBytesReturned = 0;
+ UINT16 MessageId = 0;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(ainput);
+ WINPR_ASSERT(ainput->ainput_channel);
+
+ s = ainput->buffer;
+ WINPR_ASSERT(s);
+
+ Stream_SetPosition(s, 0);
+ rc = WTSVirtualChannelRead(ainput->ainput_channel, 0, NULL, 0, &BytesReturned);
+ if (!rc)
+ goto out;
+
+ if (BytesReturned < 2)
+ {
+ error = CHANNEL_RC_OK;
+ goto out;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ if (WTSVirtualChannelRead(ainput->ainput_channel, 0, (PCHAR)Stream_Buffer(s),
+ (ULONG)Stream_Capacity(s), &ActualBytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ goto out;
+ }
+
+ if (BytesReturned != ActualBytesReturned)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead size mismatch %" PRId32 ", expected %" PRId32,
+ ActualBytesReturned, BytesReturned);
+ goto out;
+ }
+
+ Stream_SetLength(s, ActualBytesReturned);
+ Stream_Read_UINT16(s, MessageId);
+
+ switch (MessageId)
+ {
+ case MSG_AINPUT_MOUSE:
+ error = ainput_server_recv_mouse_event(ainput, s);
+ break;
+
+ default:
+ WLog_ERR(TAG, "audin_server_thread_func: unknown MessageId %" PRIu8 "", MessageId);
+ break;
+ }
+
+out:
+ if (error)
+ WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+BOOL ainput_server_context_handle(ainput_server_context* context, HANDLE* handle)
+{
+ ainput_server* ainput = (ainput_server*)context;
+ WINPR_ASSERT(ainput);
+ WINPR_ASSERT(handle);
+
+ if (!ainput->externalThread)
+ {
+ WLog_WARN(TAG, "[%s] externalThread fail!", AINPUT_DVC_CHANNEL_NAME);
+ return FALSE;
+ }
+ if (ainput->state == AINPUT_INITIAL)
+ {
+ WLog_WARN(TAG, "[%s] state fail!", AINPUT_DVC_CHANNEL_NAME);
+ return FALSE;
+ }
+ *handle = ainput_server_get_channel_handle(ainput);
+ return TRUE;
+}
+
+UINT ainput_server_context_poll_int(ainput_server_context* context)
+{
+ ainput_server* ainput = (ainput_server*)context;
+ UINT error = ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(ainput);
+
+ switch (ainput->state)
+ {
+ case AINPUT_INITIAL:
+ error = ainput_server_open_channel(ainput);
+ if (error)
+ WLog_ERR(TAG, "ainput_server_open_channel failed with error %" PRIu32 "!", error);
+ else
+ ainput->state = AINPUT_OPENED;
+ break;
+ case AINPUT_OPENED:
+ {
+ union
+ {
+ BYTE* pb;
+ void* pv;
+ } buffer;
+ DWORD BytesReturned = 0;
+
+ buffer.pv = NULL;
+
+ if (WTSVirtualChannelQuery(ainput->ainput_channel, WTSVirtualChannelReady, &buffer.pv,
+ &BytesReturned) != TRUE)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelReady failed,");
+ }
+ else
+ {
+ if (*buffer.pb != 0)
+ {
+ error = ainput_server_send_version(ainput);
+ if (error)
+ WLog_ERR(TAG, "audin_server_send_version failed with error %" PRIu32 "!",
+ error);
+ else
+ ainput->state = AINPUT_VERSION_SENT;
+ }
+ else
+ error = CHANNEL_RC_OK;
+ }
+ WTSFreeMemory(buffer.pv);
+ }
+ break;
+ case AINPUT_VERSION_SENT:
+ error = ainput_process_message(ainput);
+ break;
+
+ default:
+ WLog_ERR(TAG, "AINPUT chanel is in invalid state %d", ainput->state);
+ break;
+ }
+
+ return error;
+}
+
+UINT ainput_server_context_poll(ainput_server_context* context)
+{
+ ainput_server* ainput = (ainput_server*)context;
+
+ WINPR_ASSERT(ainput);
+ if (!ainput->externalThread)
+ {
+ WLog_WARN(TAG, "[%s] externalThread fail!", AINPUT_DVC_CHANNEL_NAME);
+ return ERROR_INTERNAL_ERROR;
+ }
+ return ainput_server_context_poll_int(context);
+}
diff --git a/channels/audin/CMakeLists.txt b/channels/audin/CMakeLists.txt
new file mode 100644
index 0000000..d72b102
--- /dev/null
+++ b/channels/audin/CMakeLists.txt
@@ -0,0 +1,26 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("audin")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/audin/ChannelOptions.cmake b/channels/audin/ChannelOptions.cmake
new file mode 100644
index 0000000..39ca402
--- /dev/null
+++ b/channels/audin/ChannelOptions.cmake
@@ -0,0 +1,17 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT ON)
+
+if(ANDROID)
+ set(OPTION_SERVER_DEFAULT OFF)
+endif()
+
+define_channel_options(NAME "audin" TYPE "dynamic"
+ DESCRIPTION "Audio Input Redirection Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPEAI]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/audin/client/CMakeLists.txt b/channels/audin/client/CMakeLists.txt
new file mode 100644
index 0000000..043e1b5
--- /dev/null
+++ b/channels/audin/client/CMakeLists.txt
@@ -0,0 +1,63 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("audin")
+
+set(${MODULE_PREFIX}_SRCS
+ audin_main.c
+ audin_main.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp winpr
+)
+
+include_directories(..)
+
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
+
+if(WITH_OSS)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "oss" "")
+endif()
+
+if(WITH_ALSA)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "alsa" "")
+endif()
+
+if(WITH_PULSE)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "pulse" "")
+endif()
+
+if(WITH_OPENSLES)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "opensles" "")
+endif()
+
+if(WITH_WINMM)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "winmm" "")
+endif()
+
+if(WITH_MACAUDIO)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "mac" "")
+endif()
+
+if(WITH_SNDIO)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "sndio" "")
+endif()
+
+if(WITH_IOSAUDIO)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "ios" "")
+ endif()
diff --git a/channels/audin/client/alsa/CMakeLists.txt b/channels/audin/client/alsa/CMakeLists.txt
new file mode 100644
index 0000000..1213f74
--- /dev/null
+++ b/channels/audin/client/alsa/CMakeLists.txt
@@ -0,0 +1,35 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("audin" "alsa" "")
+
+find_package(ALSA REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ audin_alsa.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${ALSA_LIBRARIES}
+)
+
+include_directories(..)
+include_directories(${ALSA_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/audin/client/alsa/audin_alsa.c b/channels/audin/client/alsa/audin_alsa.c
new file mode 100644
index 0000000..be3e52a
--- /dev/null
+++ b/channels/audin/client/alsa/audin_alsa.c
@@ -0,0 +1,453 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel - ALSA implementation
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/cmdline.h>
+#include <winpr/wlog.h>
+
+#include <alsa/asoundlib.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/rdpsnd.h>
+
+#include "audin_main.h"
+
+typedef struct
+{
+ IAudinDevice iface;
+
+ char* device_name;
+ UINT32 frames_per_packet;
+ AUDIO_FORMAT aformat;
+
+ HANDLE thread;
+ HANDLE stopEvent;
+
+ AudinReceive receive;
+ void* user_data;
+
+ rdpContext* rdpcontext;
+ wLog* log;
+ int bytes_per_frame;
+} AudinALSADevice;
+
+static snd_pcm_format_t audin_alsa_format(UINT32 wFormatTag, UINT32 bitPerChannel)
+{
+ switch (wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ switch (bitPerChannel)
+ {
+ case 16:
+ return SND_PCM_FORMAT_S16_LE;
+
+ case 8:
+ return SND_PCM_FORMAT_S8;
+
+ default:
+ return SND_PCM_FORMAT_UNKNOWN;
+ }
+
+ case WAVE_FORMAT_ALAW:
+ return SND_PCM_FORMAT_A_LAW;
+
+ case WAVE_FORMAT_MULAW:
+ return SND_PCM_FORMAT_MU_LAW;
+
+ default:
+ return SND_PCM_FORMAT_UNKNOWN;
+ }
+}
+
+static BOOL audin_alsa_set_params(AudinALSADevice* alsa, snd_pcm_t* capture_handle)
+{
+ int error = 0;
+ SSIZE_T s = 0;
+ UINT32 channels = alsa->aformat.nChannels;
+ snd_pcm_hw_params_t* hw_params = NULL;
+ snd_pcm_format_t format =
+ audin_alsa_format(alsa->aformat.wFormatTag, alsa->aformat.wBitsPerSample);
+
+ if ((error = snd_pcm_hw_params_malloc(&hw_params)) < 0)
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "snd_pcm_hw_params_malloc (%s)", snd_strerror(error));
+ return FALSE;
+ }
+
+ snd_pcm_hw_params_any(capture_handle, hw_params);
+ snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
+ snd_pcm_hw_params_set_format(capture_handle, hw_params, format);
+ snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &alsa->aformat.nSamplesPerSec, NULL);
+ snd_pcm_hw_params_set_channels_near(capture_handle, hw_params, &channels);
+ snd_pcm_hw_params(capture_handle, hw_params);
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_prepare(capture_handle);
+ if (channels > UINT16_MAX)
+ return FALSE;
+ s = snd_pcm_format_size(format, 1);
+ if ((s < 0) || (s > UINT16_MAX))
+ return FALSE;
+ alsa->aformat.nChannels = (UINT16)channels;
+ alsa->bytes_per_frame = (size_t)s * channels;
+ return TRUE;
+}
+
+static DWORD WINAPI audin_alsa_thread_func(LPVOID arg)
+{
+ int error = 0;
+ BYTE* buffer = NULL;
+ snd_pcm_t* capture_handle = NULL;
+ AudinALSADevice* alsa = (AudinALSADevice*)arg;
+ DWORD status = 0;
+ WLog_Print(alsa->log, WLOG_DEBUG, "in");
+
+ if ((error = snd_pcm_open(&capture_handle, alsa->device_name, SND_PCM_STREAM_CAPTURE, 0)) < 0)
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "snd_pcm_open (%s)", snd_strerror(error));
+ error = CHANNEL_RC_INITIALIZATION_ERROR;
+ goto out;
+ }
+
+ if (!audin_alsa_set_params(alsa, capture_handle))
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "audin_alsa_set_params failed");
+ goto out;
+ }
+
+ buffer =
+ (BYTE*)calloc(alsa->frames_per_packet + alsa->aformat.nBlockAlign, alsa->bytes_per_frame);
+
+ if (!buffer)
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "calloc failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ while (1)
+ {
+ size_t frames = alsa->frames_per_packet;
+ status = WaitForSingleObject(alsa->stopEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_Print(alsa->log, WLOG_ERROR, "WaitForSingleObject failed with error %ld!", error);
+ break;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ error = snd_pcm_readi(capture_handle, buffer, frames);
+
+ if (error == 0)
+ continue;
+
+ if (error == -EPIPE)
+ {
+ snd_pcm_recover(capture_handle, error, 0);
+ continue;
+ }
+ else if (error < 0)
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "snd_pcm_readi (%s)", snd_strerror(error));
+ break;
+ }
+
+ error = alsa->receive(&alsa->aformat, buffer, (long)error * alsa->bytes_per_frame,
+ alsa->user_data);
+
+ if (error)
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "audin_alsa_thread_receive failed with error %ld",
+ error);
+ break;
+ }
+ }
+
+ free(buffer);
+
+ if (capture_handle)
+ snd_pcm_close(capture_handle);
+
+out:
+ WLog_Print(alsa->log, WLOG_DEBUG, "out");
+
+ if (error && alsa->rdpcontext)
+ setChannelError(alsa->rdpcontext, error, "audin_alsa_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_alsa_free(IAudinDevice* device)
+{
+ AudinALSADevice* alsa = (AudinALSADevice*)device;
+
+ if (alsa)
+ free(alsa->device_name);
+
+ free(alsa);
+ return CHANNEL_RC_OK;
+}
+
+static BOOL audin_alsa_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
+{
+ if (!device || !format)
+ return FALSE;
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ if (format->cbSize == 0 && (format->nSamplesPerSec <= 48000) &&
+ (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
+ (format->nChannels == 1 || format->nChannels == 2))
+ {
+ return TRUE;
+ }
+
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_alsa_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
+ UINT32 FramesPerPacket)
+{
+ AudinALSADevice* alsa = (AudinALSADevice*)device;
+
+ if (!alsa || !format)
+ return ERROR_INVALID_PARAMETER;
+
+ alsa->aformat = *format;
+ alsa->frames_per_packet = FramesPerPacket;
+
+ if (audin_alsa_format(format->wFormatTag, format->wBitsPerSample) == SND_PCM_FORMAT_UNKNOWN)
+ return ERROR_INTERNAL_ERROR;
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_alsa_open(IAudinDevice* device, AudinReceive receive, void* user_data)
+{
+ AudinALSADevice* alsa = (AudinALSADevice*)device;
+
+ if (!device || !receive || !user_data)
+ return ERROR_INVALID_PARAMETER;
+
+ alsa->receive = receive;
+ alsa->user_data = user_data;
+
+ if (!(alsa->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "CreateEvent failed!");
+ goto error_out;
+ }
+
+ if (!(alsa->thread = CreateThread(NULL, 0, audin_alsa_thread_func, alsa, 0, NULL)))
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "CreateThread failed!");
+ goto error_out;
+ }
+
+ return CHANNEL_RC_OK;
+error_out:
+ CloseHandle(alsa->stopEvent);
+ alsa->stopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_alsa_close(IAudinDevice* device)
+{
+ UINT error = CHANNEL_RC_OK;
+ AudinALSADevice* alsa = (AudinALSADevice*)device;
+
+ if (!alsa)
+ return ERROR_INVALID_PARAMETER;
+
+ if (alsa->stopEvent)
+ {
+ SetEvent(alsa->stopEvent);
+
+ if (WaitForSingleObject(alsa->thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_Print(alsa->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "",
+ error);
+ return error;
+ }
+
+ CloseHandle(alsa->stopEvent);
+ alsa->stopEvent = NULL;
+ CloseHandle(alsa->thread);
+ alsa->thread = NULL;
+ }
+
+ alsa->receive = NULL;
+ alsa->user_data = NULL;
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_alsa_parse_addin_args(AudinALSADevice* device, const ADDIN_ARGV* args)
+{
+ int status = 0;
+ DWORD flags = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ AudinALSADevice* alsa = (AudinALSADevice*)device;
+ COMMAND_LINE_ARGUMENT_A audin_alsa_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
+ NULL, NULL, -1, NULL, "audio device name" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status = CommandLineParseArgumentsA(args->argc, args->argv, audin_alsa_args, flags, alsa, NULL,
+ NULL);
+
+ if (status < 0)
+ return ERROR_INVALID_PARAMETER;
+
+ arg = audin_alsa_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
+ {
+ alsa->device_name = _strdup(arg->Value);
+
+ if (!alsa->device_name)
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(
+ UINT alsa_freerdp_audin_client_subsystem_entry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ const ADDIN_ARGV* args = NULL;
+ AudinALSADevice* alsa = NULL;
+ UINT error = 0;
+ alsa = (AudinALSADevice*)calloc(1, sizeof(AudinALSADevice));
+
+ if (!alsa)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ alsa->log = WLog_Get(TAG);
+ alsa->iface.Open = audin_alsa_open;
+ alsa->iface.FormatSupported = audin_alsa_format_supported;
+ alsa->iface.SetFormat = audin_alsa_set_format;
+ alsa->iface.Close = audin_alsa_close;
+ alsa->iface.Free = audin_alsa_free;
+ alsa->rdpcontext = pEntryPoints->rdpcontext;
+ args = pEntryPoints->args;
+
+ if ((error = audin_alsa_parse_addin_args(alsa, args)))
+ {
+ WLog_Print(alsa->log, WLOG_ERROR,
+ "audin_alsa_parse_addin_args failed with errorcode %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ if (!alsa->device_name)
+ {
+ alsa->device_name = _strdup("default");
+
+ if (!alsa->device_name)
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "_strdup failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+ }
+
+ alsa->frames_per_packet = 128;
+ alsa->aformat.nChannels = 2;
+ alsa->aformat.wBitsPerSample = 16;
+ alsa->aformat.wFormatTag = WAVE_FORMAT_PCM;
+ alsa->aformat.nSamplesPerSec = 44100;
+
+ if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)alsa)))
+ {
+ WLog_Print(alsa->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!",
+ error);
+ goto error_out;
+ }
+
+ return CHANNEL_RC_OK;
+error_out:
+ free(alsa->device_name);
+ free(alsa);
+ return error;
+}
diff --git a/channels/audin/client/audin_main.c b/channels/audin/client/audin_main.c
new file mode 100644
index 0000000..1578d26
--- /dev/null
+++ b/channels/audin/client/audin_main.c
@@ -0,0 +1,1109 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2015 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <errno.h>
+#include <winpr/assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/cmdline.h>
+#include <winpr/wlog.h>
+
+#include <freerdp/addin.h>
+
+#include <winpr/stream.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/codec/dsp.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/channels/audin.h>
+
+#include "audin_main.h"
+
+#define SNDIN_VERSION 0x02
+
+typedef enum
+{
+ MSG_SNDIN_VERSION = 0x01,
+ MSG_SNDIN_FORMATS = 0x02,
+ MSG_SNDIN_OPEN = 0x03,
+ MSG_SNDIN_OPEN_REPLY = 0x04,
+ MSG_SNDIN_DATA_INCOMING = 0x05,
+ MSG_SNDIN_DATA = 0x06,
+ MSG_SNDIN_FORMATCHANGE = 0x07,
+} MSG_SNDIN;
+
+typedef struct
+{
+ IWTSVirtualChannelCallback iface;
+
+ IWTSPlugin* plugin;
+ IWTSVirtualChannelManager* channel_mgr;
+ IWTSVirtualChannel* channel;
+
+ /**
+ * The supported format list sent back to the server, which needs to
+ * be stored as reference when the server sends the format index in
+ * Open PDU and Format Change PDU
+ */
+ AUDIO_FORMAT* formats;
+ UINT32 formats_count;
+} AUDIN_CHANNEL_CALLBACK;
+
+typedef struct
+{
+ IWTSPlugin iface;
+
+ GENERIC_LISTENER_CALLBACK* listener_callback;
+
+ /* Parsed plugin data */
+ AUDIO_FORMAT* fixed_format;
+ char* subsystem;
+ char* device_name;
+
+ /* Device interface */
+ IAudinDevice* device;
+
+ rdpContext* rdpcontext;
+ BOOL attached;
+ wStream* data;
+ AUDIO_FORMAT* format;
+ UINT32 FramesPerPacket;
+
+ FREERDP_DSP_CONTEXT* dsp_context;
+ wLog* log;
+
+ IWTSListener* listener;
+
+ BOOL initialized;
+ UINT32 version;
+} AUDIN_PLUGIN;
+
+static BOOL audin_process_addin_args(AUDIN_PLUGIN* audin, const ADDIN_ARGV* args);
+
+static UINT audin_channel_write_and_free(AUDIN_CHANNEL_CALLBACK* callback, wStream* out,
+ BOOL freeStream)
+{
+ if (!callback || !out)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!callback->channel || !callback->channel->Write)
+ return ERROR_INTERNAL_ERROR;
+
+ Stream_SealLength(out);
+ WINPR_ASSERT(Stream_Length(out) <= ULONG_MAX);
+ const UINT error = callback->channel->Write(callback->channel, (ULONG)Stream_Length(out),
+ Stream_Buffer(out), NULL);
+
+ if (freeStream)
+ Stream_Free(out, TRUE);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_process_version(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ const UINT32 ClientVersion = SNDIN_VERSION;
+ UINT32 ServerVersion = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, ServerVersion);
+ WLog_Print(audin->log, WLOG_DEBUG, "ServerVersion=%" PRIu32 ", ClientVersion=%" PRIu32,
+ ServerVersion, ClientVersion);
+
+ /* Do not answer server packet, we do not support the channel version. */
+ if (ServerVersion > ClientVersion)
+ {
+ WLog_Print(audin->log, WLOG_WARN,
+ "Incompatible channel version server=%" PRIu32
+ ", client supports version=%" PRIu32,
+ ServerVersion, ClientVersion);
+ return CHANNEL_RC_OK;
+ }
+ audin->version = ServerVersion;
+
+ wStream* out = Stream_New(NULL, 5);
+
+ if (!out)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
+ return ERROR_OUTOFMEMORY;
+ }
+
+ Stream_Write_UINT8(out, MSG_SNDIN_VERSION);
+ Stream_Write_UINT32(out, ClientVersion);
+ return audin_channel_write_and_free(callback, out, TRUE);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_send_incoming_data_pdu(AUDIN_CHANNEL_CALLBACK* callback)
+{
+ BYTE out_data[1] = { MSG_SNDIN_DATA_INCOMING };
+
+ if (!callback || !callback->channel || !callback->channel->Write)
+ return ERROR_INTERNAL_ERROR;
+
+ return callback->channel->Write(callback->channel, 1, out_data, NULL);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_process_formats(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ UINT error = ERROR_INTERNAL_ERROR;
+ UINT32 NumFormats = 0;
+ UINT32 cbSizeFormatsPacket = 0;
+
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(callback);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, NumFormats);
+ WLog_Print(audin->log, WLOG_DEBUG, "NumFormats %" PRIu32 "", NumFormats);
+
+ if ((NumFormats < 1) || (NumFormats > 1000))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "bad NumFormats %" PRIu32 "", NumFormats);
+ return ERROR_INVALID_DATA;
+ }
+
+ Stream_Seek_UINT32(s); /* cbSizeFormatsPacket */
+ callback->formats = audio_formats_new(NumFormats);
+
+ if (!callback->formats)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "calloc failed!");
+ return ERROR_INVALID_DATA;
+ }
+
+ wStream* out = Stream_New(NULL, 9);
+
+ if (!out)
+ {
+ error = CHANNEL_RC_NO_MEMORY;
+ WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
+ goto out;
+ }
+
+ Stream_Seek(out, 9);
+
+ /* SoundFormats (variable) */
+ for (UINT32 i = 0; i < NumFormats; i++)
+ {
+ AUDIO_FORMAT format = { 0 };
+
+ if (!audio_format_read(s, &format))
+ {
+ error = ERROR_INVALID_DATA;
+ goto out;
+ }
+
+ audio_format_print(audin->log, WLOG_DEBUG, &format);
+
+ if (!audio_format_compatible(audin->fixed_format, &format))
+ {
+ audio_format_free(&format);
+ continue;
+ }
+
+ if (freerdp_dsp_supports_format(&format, TRUE) ||
+ audin->device->FormatSupported(audin->device, &format))
+ {
+ /* Store the agreed format in the corresponding index */
+ callback->formats[callback->formats_count++] = format;
+
+ if (!audio_format_write(out, &format))
+ {
+ error = CHANNEL_RC_NO_MEMORY;
+ WLog_Print(audin->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!");
+ goto out;
+ }
+ }
+ else
+ {
+ audio_format_free(&format);
+ }
+ }
+
+ if ((error = audin_send_incoming_data_pdu(callback)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "audin_send_incoming_data_pdu failed!");
+ goto out;
+ }
+
+ cbSizeFormatsPacket = (UINT32)Stream_GetPosition(out);
+ Stream_SetPosition(out, 0);
+ Stream_Write_UINT8(out, MSG_SNDIN_FORMATS); /* Header (1 byte) */
+ Stream_Write_UINT32(out, callback->formats_count); /* NumFormats (4 bytes) */
+ Stream_Write_UINT32(out, cbSizeFormatsPacket); /* cbSizeFormatsPacket (4 bytes) */
+ Stream_SetPosition(out, cbSizeFormatsPacket);
+ error = audin_channel_write_and_free(callback, out, FALSE);
+out:
+
+ if (error != CHANNEL_RC_OK)
+ {
+ audio_formats_free(callback->formats, NumFormats);
+ callback->formats = NULL;
+ }
+
+ Stream_Free(out, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_send_format_change_pdu(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback,
+ UINT32 NewFormat)
+{
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(callback);
+
+ wStream* out = Stream_New(NULL, 5);
+
+ if (!out)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_OK;
+ }
+
+ Stream_Write_UINT8(out, MSG_SNDIN_FORMATCHANGE);
+ Stream_Write_UINT32(out, NewFormat);
+ return audin_channel_write_and_free(callback, out, TRUE);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_send_open_reply_pdu(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback,
+ UINT32 Result)
+{
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(callback);
+
+ wStream* out = Stream_New(NULL, 5);
+
+ if (!out)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT8(out, MSG_SNDIN_OPEN_REPLY);
+ Stream_Write_UINT32(out, Result);
+ return audin_channel_write_and_free(callback, out, TRUE);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_receive_wave_data(const AUDIO_FORMAT* format, const BYTE* data, size_t size,
+ void* user_data)
+{
+ WINPR_ASSERT(format);
+
+ UINT error = ERROR_INTERNAL_ERROR;
+ AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*)user_data;
+
+ if (!callback)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)callback->plugin;
+
+ if (!audin)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ if (!audin->attached)
+ return CHANNEL_RC_OK;
+
+ Stream_SetPosition(audin->data, 0);
+
+ if (!Stream_EnsureRemainingCapacity(audin->data, 1))
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT8(audin->data, MSG_SNDIN_DATA);
+
+ const BOOL compatible = audio_format_compatible(format, audin->format);
+ if (compatible && audin->device->FormatSupported(audin->device, audin->format))
+ {
+ if (!Stream_EnsureRemainingCapacity(audin->data, size))
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write(audin->data, data, size);
+ }
+ else
+ {
+ if (!freerdp_dsp_encode(audin->dsp_context, format, data, size, audin->data))
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Did not encode anything, skip this, the codec is not ready for output. */
+ if (Stream_GetPosition(audin->data) <= 1)
+ return CHANNEL_RC_OK;
+
+ audio_format_print(audin->log, WLOG_TRACE, audin->format);
+ WLog_Print(audin->log, WLOG_TRACE, "[%" PRIdz "/%" PRIdz "]", size,
+ Stream_GetPosition(audin->data) - 1);
+
+ if ((error = audin_send_incoming_data_pdu(callback)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "audin_send_incoming_data_pdu failed!");
+ return error;
+ }
+
+ return audin_channel_write_and_free(callback, audin->data, FALSE);
+}
+
+static BOOL audin_open_device(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback)
+{
+ UINT error = ERROR_INTERNAL_ERROR;
+ AUDIO_FORMAT format = { 0 };
+
+ if (!audin || !audin->device)
+ return FALSE;
+
+ format = *audin->format;
+ const BOOL supported =
+ IFCALLRESULT(FALSE, audin->device->FormatSupported, audin->device, &format);
+ WLog_Print(audin->log, WLOG_DEBUG, "microphone uses %s codec",
+ audio_format_get_tag_string(format.wFormatTag));
+
+ if (!supported)
+ {
+ /* Default sample rates supported by most backends. */
+ const UINT32 samplerates[] = { format.nSamplesPerSec, 96000, 48000, 44100, 22050 };
+ BOOL test = FALSE;
+
+ format.wFormatTag = WAVE_FORMAT_PCM;
+ format.wBitsPerSample = 16;
+ format.cbSize = 0;
+ for (size_t x = 0; x < ARRAYSIZE(samplerates); x++)
+ {
+ format.nSamplesPerSec = samplerates[x];
+ for (UINT16 y = audin->format->nChannels; y > 0; y--)
+ {
+ format.nChannels = y;
+ format.nBlockAlign = 2 * format.nChannels;
+ test = IFCALLRESULT(FALSE, audin->device->FormatSupported, audin->device, &format);
+ if (test)
+ break;
+ }
+ if (test)
+ break;
+ }
+ if (!test)
+ return FALSE;
+ }
+
+ IFCALLRET(audin->device->SetFormat, error, audin->device, &format, audin->FramesPerPacket);
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "SetFormat failed with errorcode %" PRIu32 "", error);
+ return FALSE;
+ }
+
+ if (!freerdp_dsp_context_reset(audin->dsp_context, audin->format, audin->FramesPerPacket))
+ return FALSE;
+
+ IFCALLRET(audin->device->Open, error, audin->device, audin_receive_wave_data, callback);
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "Open failed with errorcode %" PRIu32 "", error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_process_open(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ UINT32 initialFormat = 0;
+ UINT32 FramesPerPacket = 0;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, FramesPerPacket);
+ Stream_Read_UINT32(s, initialFormat);
+ WLog_Print(audin->log, WLOG_DEBUG, "FramesPerPacket=%" PRIu32 " initialFormat=%" PRIu32 "",
+ FramesPerPacket, initialFormat);
+ audin->FramesPerPacket = FramesPerPacket;
+
+ if (initialFormat >= callback->formats_count)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "invalid format index %" PRIu32 " (total %" PRIu32 ")",
+ initialFormat, callback->formats_count);
+ return ERROR_INVALID_DATA;
+ }
+
+ audin->format = &callback->formats[initialFormat];
+
+ if (!audin_open_device(audin, callback))
+ return ERROR_INTERNAL_ERROR;
+
+ if ((error = audin_send_format_change_pdu(audin, callback, initialFormat)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "audin_send_format_change_pdu failed!");
+ return error;
+ }
+
+ if ((error = audin_send_open_reply_pdu(audin, callback, 0)))
+ WLog_Print(audin->log, WLOG_ERROR, "audin_send_open_reply_pdu failed!");
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_process_format_change(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback,
+ wStream* s)
+{
+ UINT32 NewFormat = 0;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, NewFormat);
+ WLog_Print(audin->log, WLOG_DEBUG, "NewFormat=%" PRIu32 "", NewFormat);
+
+ if (NewFormat >= callback->formats_count)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "invalid format index %" PRIu32 " (total %" PRIu32 ")",
+ NewFormat, callback->formats_count);
+ return ERROR_INVALID_DATA;
+ }
+
+ audin->format = &callback->formats[NewFormat];
+
+ if (audin->device)
+ {
+ IFCALLRET(audin->device->Close, error, audin->device);
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "Close failed with errorcode %" PRIu32 "", error);
+ return error;
+ }
+ }
+
+ if (!audin_open_device(audin, callback))
+ return ERROR_INTERNAL_ERROR;
+
+ if ((error = audin_send_format_change_pdu(audin, callback, NewFormat)))
+ WLog_ERR(TAG, "audin_send_format_change_pdu failed!");
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
+{
+ UINT error = 0;
+ BYTE MessageId = 0;
+ AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*)pChannelCallback;
+
+ if (!callback || !data)
+ return ERROR_INVALID_PARAMETER;
+
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)callback->plugin;
+
+ if (!audin)
+ return ERROR_INTERNAL_ERROR;
+
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, data, 1))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT8(data, MessageId);
+ WLog_Print(audin->log, WLOG_DEBUG, "MessageId=0x%02" PRIx8 "", MessageId);
+
+ switch (MessageId)
+ {
+ case MSG_SNDIN_VERSION:
+ error = audin_process_version(audin, callback, data);
+ break;
+
+ case MSG_SNDIN_FORMATS:
+ error = audin_process_formats(audin, callback, data);
+ break;
+
+ case MSG_SNDIN_OPEN:
+ error = audin_process_open(audin, callback, data);
+ break;
+
+ case MSG_SNDIN_FORMATCHANGE:
+ error = audin_process_format_change(audin, callback, data);
+ break;
+
+ default:
+ WLog_Print(audin->log, WLOG_ERROR, "unknown MessageId=0x%02" PRIx8 "", MessageId);
+ error = ERROR_INVALID_DATA;
+ break;
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_on_close(IWTSVirtualChannelCallback* pChannelCallback)
+{
+ AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*)pChannelCallback;
+ WINPR_ASSERT(callback);
+
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(audin);
+
+ UINT error = CHANNEL_RC_OK;
+ WLog_Print(audin->log, WLOG_TRACE, "...");
+
+ if (audin->device)
+ {
+ IFCALLRET(audin->device->Close, error, audin->device);
+
+ if (error != CHANNEL_RC_OK)
+ WLog_Print(audin->log, WLOG_ERROR, "Close failed with errorcode %" PRIu32 "", error);
+ }
+
+ audin->format = NULL;
+ audio_formats_free(callback->formats, callback->formats_count);
+ free(callback);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
+ IWTSVirtualChannel* pChannel, BYTE* Data,
+ BOOL* pbAccept, IWTSVirtualChannelCallback** ppCallback)
+{
+ GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback;
+
+ if (!listener_callback || !listener_callback->plugin)
+ return ERROR_INTERNAL_ERROR;
+
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)listener_callback->plugin;
+ WLog_Print(audin->log, WLOG_TRACE, "...");
+ AUDIN_CHANNEL_CALLBACK* callback =
+ (AUDIN_CHANNEL_CALLBACK*)calloc(1, sizeof(AUDIN_CHANNEL_CALLBACK));
+
+ if (!callback)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ callback->iface.OnDataReceived = audin_on_data_received;
+ callback->iface.OnClose = audin_on_close;
+ callback->plugin = listener_callback->plugin;
+ callback->channel_mgr = listener_callback->channel_mgr;
+ callback->channel = pChannel;
+ *ppCallback = (IWTSVirtualChannelCallback*)callback;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr)
+{
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
+
+ if (!audin)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ if (!pChannelMgr)
+ return ERROR_INVALID_PARAMETER;
+
+ if (audin->initialized)
+ {
+ WLog_ERR(TAG, "[%s] channel initialized twice, aborting", AUDIN_DVC_CHANNEL_NAME);
+ return ERROR_INVALID_DATA;
+ }
+
+ WLog_Print(audin->log, WLOG_TRACE, "...");
+ audin->listener_callback =
+ (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK));
+
+ if (!audin->listener_callback)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ audin->listener_callback->iface.OnNewChannelConnection = audin_on_new_channel_connection;
+ audin->listener_callback->plugin = pPlugin;
+ audin->listener_callback->channel_mgr = pChannelMgr;
+ const UINT rc = pChannelMgr->CreateListener(pChannelMgr, AUDIN_DVC_CHANNEL_NAME, 0,
+ &audin->listener_callback->iface, &audin->listener);
+
+ audin->initialized = rc == CHANNEL_RC_OK;
+ return rc;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_plugin_terminated(IWTSPlugin* pPlugin)
+{
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!audin)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ WLog_Print(audin->log, WLOG_TRACE, "...");
+
+ if (audin->listener_callback)
+ {
+ IWTSVirtualChannelManager* mgr = audin->listener_callback->channel_mgr;
+ if (mgr)
+ IFCALL(mgr->DestroyListener, mgr, audin->listener);
+ }
+ audio_formats_free(audin->fixed_format, 1);
+
+ if (audin->device)
+ {
+ IFCALLRET(audin->device->Free, error, audin->device);
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Free failed with errorcode %" PRIu32 "", error);
+ // dont stop on error
+ }
+
+ audin->device = NULL;
+ }
+
+ freerdp_dsp_context_free(audin->dsp_context);
+ Stream_Free(audin->data, TRUE);
+ free(audin->subsystem);
+ free(audin->device_name);
+ free(audin->listener_callback);
+ free(audin);
+ return CHANNEL_RC_OK;
+}
+
+static UINT audin_plugin_attached(IWTSPlugin* pPlugin)
+{
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!audin)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ audin->attached = TRUE;
+ return error;
+}
+
+static UINT audin_plugin_detached(IWTSPlugin* pPlugin)
+{
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!audin)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ audin->attached = FALSE;
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_register_device_plugin(IWTSPlugin* pPlugin, IAudinDevice* device)
+{
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
+
+ WINPR_ASSERT(audin);
+
+ if (audin->device)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "existing device, abort.");
+ return ERROR_ALREADY_EXISTS;
+ }
+
+ WLog_Print(audin->log, WLOG_DEBUG, "device registered.");
+ audin->device = device;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_load_device_plugin(AUDIN_PLUGIN* audin, const char* name, const ADDIN_ARGV* args)
+{
+ WINPR_ASSERT(audin);
+
+ FREERDP_AUDIN_DEVICE_ENTRY_POINTS entryPoints = { 0 };
+ UINT error = ERROR_INTERNAL_ERROR;
+ const PFREERDP_AUDIN_DEVICE_ENTRY entry =
+ (const PFREERDP_AUDIN_DEVICE_ENTRY)freerdp_load_channel_addin_entry(AUDIN_CHANNEL_NAME,
+ name, NULL, 0);
+
+ if (entry == NULL)
+ {
+ WLog_Print(audin->log, WLOG_ERROR,
+ "freerdp_load_channel_addin_entry did not return any function pointers for %s ",
+ name);
+ return ERROR_INVALID_FUNCTION;
+ }
+
+ entryPoints.plugin = &audin->iface;
+ entryPoints.pRegisterAudinDevice = audin_register_device_plugin;
+ entryPoints.args = args;
+ entryPoints.rdpcontext = audin->rdpcontext;
+
+ if ((error = entry(&entryPoints)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "%s entry returned error %" PRIu32 ".", name, error);
+ return error;
+ }
+
+ WLog_Print(audin->log, WLOG_INFO, "Loaded %s backend for audin", name);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_set_subsystem(AUDIN_PLUGIN* audin, const char* subsystem)
+{
+ WINPR_ASSERT(audin);
+
+ free(audin->subsystem);
+ audin->subsystem = _strdup(subsystem);
+
+ if (!audin->subsystem)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "_strdup failed!");
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_set_device_name(AUDIN_PLUGIN* audin, const char* device_name)
+{
+ WINPR_ASSERT(audin);
+
+ free(audin->device_name);
+ audin->device_name = _strdup(device_name);
+
+ if (!audin->device_name)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "_strdup failed!");
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+BOOL audin_process_addin_args(AUDIN_PLUGIN* audin, const ADDIN_ARGV* args)
+{
+ COMMAND_LINE_ARGUMENT_A audin_args[] = {
+ { "sys", COMMAND_LINE_VALUE_REQUIRED, "<subsystem>", NULL, NULL, -1, NULL, "subsystem" },
+ { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", NULL, NULL, -1, NULL, "device" },
+ { "format", COMMAND_LINE_VALUE_REQUIRED, "<format>", NULL, NULL, -1, NULL, "format" },
+ { "rate", COMMAND_LINE_VALUE_REQUIRED, "<rate>", NULL, NULL, -1, NULL, "rate" },
+ { "channel", COMMAND_LINE_VALUE_REQUIRED, "<channel>", NULL, NULL, -1, NULL, "channel" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
+ };
+
+ if (!args || args->argc == 1)
+ return TRUE;
+
+ const DWORD flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ const int status =
+ CommandLineParseArgumentsA(args->argc, args->argv, audin_args, flags, audin, NULL, NULL);
+
+ if (status != 0)
+ return FALSE;
+
+ const COMMAND_LINE_ARGUMENT_A* arg = audin_args;
+ errno = 0;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "sys")
+ {
+ const UINT error = audin_set_subsystem(audin, arg->Value);
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(audin->log, WLOG_ERROR,
+ "audin_set_subsystem failed with error %" PRIu32 "!", error);
+ return FALSE;
+ }
+ }
+ CommandLineSwitchCase(arg, "dev")
+ {
+ const UINT error = audin_set_device_name(audin, arg->Value);
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(audin->log, WLOG_ERROR,
+ "audin_set_device_name failed with error %" PRIu32 "!", error);
+ return FALSE;
+ }
+ }
+ CommandLineSwitchCase(arg, "format")
+ {
+ unsigned long val = strtoul(arg->Value, NULL, 0);
+
+ if ((errno != 0) || (val > UINT16_MAX))
+ return FALSE;
+
+ audin->fixed_format->wFormatTag = (UINT16)val;
+ }
+ CommandLineSwitchCase(arg, "rate")
+ {
+ long val = strtol(arg->Value, NULL, 0);
+
+ if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
+ return FALSE;
+
+ audin->fixed_format->nSamplesPerSec = val;
+ }
+ CommandLineSwitchCase(arg, "channel")
+ {
+ unsigned long val = strtoul(arg->Value, NULL, 0);
+
+ if ((errno != 0) || (val <= UINT16_MAX))
+ audin->fixed_format->nChannels = (UINT16)val;
+ }
+ CommandLineSwitchDefault(arg)
+ {
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT audin_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
+{
+ struct SubsystemEntry
+ {
+ char* subsystem;
+ char* device;
+ };
+ UINT error = CHANNEL_RC_INITIALIZATION_ERROR;
+ struct SubsystemEntry entries[] =
+ {
+#if defined(WITH_PULSE)
+ { "pulse", "" },
+#endif
+#if defined(WITH_OSS)
+ { "oss", "default" },
+#endif
+#if defined(WITH_ALSA)
+ { "alsa", "default" },
+#endif
+#if defined(WITH_OPENSLES)
+ { "opensles", "default" },
+#endif
+#if defined(WITH_WINMM)
+ { "winmm", "default" },
+#endif
+#if defined(WITH_MACAUDIO)
+ { "mac", "default" },
+#endif
+#if defined(WITH_IOSAUDIO)
+ { "ios", "default" },
+#endif
+#if defined(WITH_SNDIO)
+ { "sndio", "default" },
+#endif
+ { NULL, NULL }
+ };
+ struct SubsystemEntry* entry = &entries[0];
+ WINPR_ASSERT(pEntryPoints);
+ WINPR_ASSERT(pEntryPoints->GetPlugin);
+ AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, AUDIN_CHANNEL_NAME);
+
+ if (audin != NULL)
+ return CHANNEL_RC_ALREADY_INITIALIZED;
+
+ audin = (AUDIN_PLUGIN*)calloc(1, sizeof(AUDIN_PLUGIN));
+
+ if (!audin)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ audin->log = WLog_Get(TAG);
+ audin->data = Stream_New(NULL, 4096);
+ audin->fixed_format = audio_format_new();
+
+ if (!audin->fixed_format)
+ goto out;
+
+ if (!audin->data)
+ goto out;
+
+ audin->dsp_context = freerdp_dsp_context_new(TRUE);
+
+ if (!audin->dsp_context)
+ goto out;
+
+ audin->attached = TRUE;
+ audin->iface.Initialize = audin_plugin_initialize;
+ audin->iface.Connected = NULL;
+ audin->iface.Disconnected = NULL;
+ audin->iface.Terminated = audin_plugin_terminated;
+ audin->iface.Attached = audin_plugin_attached;
+ audin->iface.Detached = audin_plugin_detached;
+
+ const ADDIN_ARGV* args = pEntryPoints->GetPluginData(pEntryPoints);
+ audin->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints);
+
+ if (args)
+ {
+ if (!audin_process_addin_args(audin, args))
+ goto out;
+ }
+
+ if (audin->subsystem)
+ {
+ if ((error = audin_load_device_plugin(audin, audin->subsystem, args)))
+ {
+ WLog_Print(
+ audin->log, WLOG_ERROR,
+ "Unable to load microphone redirection subsystem %s because of error %" PRIu32 "",
+ audin->subsystem, error);
+ goto out;
+ }
+ }
+ else
+ {
+ while (entry && entry->subsystem && !audin->device)
+ {
+ if ((error = audin_set_subsystem(audin, entry->subsystem)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR,
+ "audin_set_subsystem for %s failed with error %" PRIu32 "!",
+ entry->subsystem, error);
+ }
+ else if ((error = audin_set_device_name(audin, entry->device)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR,
+ "audin_set_device_name for %s failed with error %" PRIu32 "!",
+ entry->subsystem, error);
+ }
+ else if ((error = audin_load_device_plugin(audin, audin->subsystem, args)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR,
+ "audin_load_device_plugin %s failed with error %" PRIu32 "!",
+ entry->subsystem, error);
+ }
+
+ entry++;
+ }
+ }
+
+ if (audin->device == NULL)
+ {
+ /* If we have no audin device do not register plugin but still return OK or the client will
+ * just disconnect due to a missing microphone. */
+ WLog_Print(audin->log, WLOG_ERROR, "No microphone device could be found.");
+ error = CHANNEL_RC_OK;
+ goto out;
+ }
+
+ error = pEntryPoints->RegisterPlugin(pEntryPoints, AUDIN_CHANNEL_NAME, &audin->iface);
+ if (error == CHANNEL_RC_OK)
+ return error;
+
+out:
+ audin_plugin_terminated(&audin->iface);
+ return error;
+}
diff --git a/channels/audin/client/audin_main.h b/channels/audin/client/audin_main.h
new file mode 100644
index 0000000..1e6a498
--- /dev/null
+++ b/channels/audin/client/audin_main.h
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H
+
+#include <freerdp/config.h>
+
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/client/audin.h>
+
+#define TAG CHANNELS_TAG("audin.client")
+
+#endif /* FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H */
diff --git a/channels/audin/client/ios/CMakeLists.txt b/channels/audin/client/ios/CMakeLists.txt
new file mode 100644
index 0000000..e6ec3ad
--- /dev/null
+++ b/channels/audin/client/ios/CMakeLists.txt
@@ -0,0 +1,39 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+ # FreeRDP cmake build script
+ #
+ # Copyright (c) 2015 Armin Novak <armin.novak@thincast.com>
+ # Copyright (c) 2015 Thincast Technologies GmbH
+ #
+ # Licensed under the Apache License, Version 2.0 (the "License");
+ # you may not use this file except in compliance with the License.
+ # You may obtain a copy of the License at
+ #
+ # http://www.apache.org/licenses/LICENSE-2.0
+ #
+ # Unless required by applicable law or agreed to in writing, software
+ # distributed under the License is distributed on an "AS IS" BASIS,
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ # See the License for the specific language governing permissions and
+ # limitations under the License.
+
+ define_channel_client_subsystem("audin" "ios" "")
+ FIND_LIBRARY(CORE_AUDIO CoreAudio)
+ FIND_LIBRARY(AVFOUNDATION AVFoundation)
+ FIND_LIBRARY(AUDIO_TOOL AudioToolbox)
+
+ set(${MODULE_PREFIX}_SRCS
+ audin_ios.m
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${AVFOUNDATION}
+ ${CORE_AUDIO}
+ ${AUDIO_TOOL}
+)
+
+include_directories(..)
+include_directories(${MAC_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/audin/client/ios/audin_ios.m b/channels/audin/client/ios/audin_ios.m
new file mode 100644
index 0000000..ae30aee
--- /dev/null
+++ b/channels/audin/client/ios/audin_ios.m
@@ -0,0 +1,335 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel - iOS implementation
+ *
+ * Copyright (c) 2015 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/string.h>
+#include <winpr/thread.h>
+#include <winpr/debug.h>
+#include <winpr/cmdline.h>
+
+#import <AVFoundation/AVFoundation.h>
+
+#define __COREFOUNDATION_CFPLUGINCOM__ 1
+#define IUNKNOWN_C_GUTS \
+ void *_reserved; \
+ void *QueryInterface; \
+ void *AddRef; \
+ void *Release
+
+#include <CoreAudio/CoreAudioTypes.h>
+#include <AudioToolbox/AudioToolbox.h>
+#include <AudioToolbox/AudioQueue.h>
+
+#include <freerdp/addin.h>
+#include <freerdp/channels/rdpsnd.h>
+
+#include "audin_main.h"
+
+#define IOS_AUDIO_QUEUE_NUM_BUFFERS 100
+
+typedef struct
+{
+ IAudinDevice iface;
+
+ AUDIO_FORMAT format;
+ UINT32 FramesPerPacket;
+ int dev_unit;
+
+ AudinReceive receive;
+ void *user_data;
+
+ rdpContext *rdpcontext;
+
+ bool isOpen;
+ AudioQueueRef audioQueue;
+ AudioStreamBasicDescription audioFormat;
+ AudioQueueBufferRef audioBuffers[IOS_AUDIO_QUEUE_NUM_BUFFERS];
+} AudinIosDevice;
+
+static AudioFormatID audin_ios_get_format(const AUDIO_FORMAT *format)
+{
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ return kAudioFormatLinearPCM;
+
+ default:
+ return 0;
+ }
+}
+
+static AudioFormatFlags audin_ios_get_flags_for_format(const AUDIO_FORMAT *format)
+{
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ return kAudioFormatFlagIsSignedInteger;
+
+ default:
+ return 0;
+ }
+}
+
+static BOOL audin_ios_format_supported(IAudinDevice *device, const AUDIO_FORMAT *format)
+{
+ AudinIosDevice *ios = (AudinIosDevice *)device;
+ AudioFormatID req_fmt = 0;
+
+ if (device == NULL || format == NULL)
+ return FALSE;
+
+ req_fmt = audin_ios_get_format(format);
+
+ if (req_fmt == 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_ios_set_format(IAudinDevice *device, const AUDIO_FORMAT *format,
+ UINT32 FramesPerPacket)
+{
+ AudinIosDevice *ios = (AudinIosDevice *)device;
+
+ if (device == NULL || format == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ ios->FramesPerPacket = FramesPerPacket;
+ ios->format = *format;
+ WLog_INFO(TAG, "Audio Format %s [channels=%d, samples=%d, bits=%d]",
+ audio_format_get_tag_string(format->wFormatTag), format->nChannels,
+ format->nSamplesPerSec, format->wBitsPerSample);
+ ios->audioFormat.mBitsPerChannel = format->wBitsPerSample;
+
+ if (format->wBitsPerSample == 0)
+ ios->audioFormat.mBitsPerChannel = 16;
+
+ ios->audioFormat.mChannelsPerFrame = ios->format.nChannels;
+ ios->audioFormat.mFramesPerPacket = 1;
+
+ ios->audioFormat.mBytesPerFrame =
+ ios->audioFormat.mChannelsPerFrame * (ios->audioFormat.mBitsPerChannel / 8);
+ ios->audioFormat.mBytesPerPacket =
+ ios->audioFormat.mBytesPerFrame * ios->audioFormat.mFramesPerPacket;
+
+ ios->audioFormat.mFormatFlags = audin_ios_get_flags_for_format(format);
+ ios->audioFormat.mFormatID = audin_ios_get_format(format);
+ ios->audioFormat.mReserved = 0;
+ ios->audioFormat.mSampleRate = ios->format.nSamplesPerSec;
+ return CHANNEL_RC_OK;
+}
+
+static void ios_audio_queue_input_cb(void *aqData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
+ const AudioTimeStamp *inStartTime, UInt32 inNumPackets,
+ const AudioStreamPacketDescription *inPacketDesc)
+{
+ AudinIosDevice *ios = (AudinIosDevice *)aqData;
+ UINT error = CHANNEL_RC_OK;
+ const BYTE *buffer = inBuffer->mAudioData;
+ int buffer_size = inBuffer->mAudioDataByteSize;
+ (void)inAQ;
+ (void)inStartTime;
+ (void)inNumPackets;
+ (void)inPacketDesc;
+
+ if (buffer_size > 0)
+ error = ios->receive(&ios->format, buffer, buffer_size, ios->user_data);
+
+ AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "ios->receive failed with error %" PRIu32 "", error);
+ SetLastError(ERROR_INTERNAL_ERROR);
+ }
+}
+
+static UINT audin_ios_close(IAudinDevice *device)
+{
+ UINT errCode = CHANNEL_RC_OK;
+ char errString[1024];
+ OSStatus devStat;
+ AudinIosDevice *ios = (AudinIosDevice *)device;
+
+ if (device == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if (ios->isOpen)
+ {
+ devStat = AudioQueueStop(ios->audioQueue, true);
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueStop failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ }
+
+ ios->isOpen = false;
+ }
+
+ if (ios->audioQueue)
+ {
+ devStat = AudioQueueDispose(ios->audioQueue, true);
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueDispose failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ }
+
+ ios->audioQueue = NULL;
+ }
+
+ ios->receive = NULL;
+ ios->user_data = NULL;
+ return errCode;
+}
+
+static UINT audin_ios_open(IAudinDevice *device, AudinReceive receive, void *user_data)
+{
+ AudinIosDevice *ios = (AudinIosDevice *)device;
+ DWORD errCode;
+ char errString[1024];
+ OSStatus devStat;
+
+ ios->receive = receive;
+ ios->user_data = user_data;
+ devStat = AudioQueueNewInput(&(ios->audioFormat), ios_audio_queue_input_cb, ios, NULL,
+ kCFRunLoopCommonModes, 0, &(ios->audioQueue));
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueNewInput failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ goto err_out;
+ }
+
+ for (size_t index = 0; index < IOS_AUDIO_QUEUE_NUM_BUFFERS; index++)
+ {
+ devStat = AudioQueueAllocateBuffer(ios->audioQueue,
+ ios->FramesPerPacket * 2 * ios->format.nChannels,
+ &ios->audioBuffers[index]);
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueAllocateBuffer failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ goto err_out;
+ }
+
+ devStat = AudioQueueEnqueueBuffer(ios->audioQueue, ios->audioBuffers[index], 0, NULL);
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueEnqueueBuffer failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ goto err_out;
+ }
+ }
+
+ devStat = AudioQueueStart(ios->audioQueue, NULL);
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueStart failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ goto err_out;
+ }
+
+ ios->isOpen = true;
+ return CHANNEL_RC_OK;
+err_out:
+ audin_ios_close(device);
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+}
+
+static UINT audin_ios_free(IAudinDevice *device)
+{
+ AudinIosDevice *ios = (AudinIosDevice *)device;
+ int error;
+
+ if (device == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = audin_ios_close(device)))
+ {
+ WLog_ERR(TAG, "audin_oss_close failed with error code %d!", error);
+ }
+
+ free(ios);
+ return CHANNEL_RC_OK;
+}
+
+FREERDP_ENTRY_POINT(
+ UINT ios_freerdp_audin_client_subsystem_entry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ DWORD errCode;
+ char errString[1024];
+ const ADDIN_ARGV *args;
+ AudinIosDevice *ios;
+ UINT error;
+ ios = (AudinIosDevice *)calloc(1, sizeof(AudinIosDevice));
+
+ if (!ios)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "calloc failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ ios->iface.Open = audin_ios_open;
+ ios->iface.FormatSupported = audin_ios_format_supported;
+ ios->iface.SetFormat = audin_ios_set_format;
+ ios->iface.Close = audin_ios_close;
+ ios->iface.Free = audin_ios_free;
+ ios->rdpcontext = pEntryPoints->rdpcontext;
+ ios->dev_unit = -1;
+ args = pEntryPoints->args;
+
+ if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice *)ios)))
+ {
+ WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ return CHANNEL_RC_OK;
+error_out:
+ free(ios);
+ return error;
+}
diff --git a/channels/audin/client/mac/CMakeLists.txt b/channels/audin/client/mac/CMakeLists.txt
new file mode 100644
index 0000000..6b3e792
--- /dev/null
+++ b/channels/audin/client/mac/CMakeLists.txt
@@ -0,0 +1,41 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright (c) 2015 Armin Novak <armin.novak@thincast.com>
+# Copyright (c) 2015 Thincast Technologies GmbH
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("audin" "mac" "")
+FIND_LIBRARY(CORE_AUDIO CoreAudio)
+FIND_LIBRARY(AVFOUNDATION AVFoundation)
+FIND_LIBRARY(AUDIO_TOOL AudioToolbox)
+FIND_LIBRARY(APP_SERVICES ApplicationServices)
+
+set(${MODULE_PREFIX}_SRCS
+ audin_mac.m
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${AVFOUNDATION}
+ ${CORE_AUDIO}
+ ${AUDIO_TOOL}
+ ${APP_SERVICES}
+)
+
+include_directories(..)
+include_directories(${MAC_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/audin/client/mac/audin_mac.m b/channels/audin/client/mac/audin_mac.m
new file mode 100644
index 0000000..19749a9
--- /dev/null
+++ b/channels/audin/client/mac/audin_mac.m
@@ -0,0 +1,463 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel - Mac OS X implementation
+ *
+ * Copyright (c) 2015 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/string.h>
+#include <winpr/thread.h>
+#include <winpr/debug.h>
+#include <winpr/cmdline.h>
+
+#import <AVFoundation/AVFoundation.h>
+
+#define __COREFOUNDATION_CFPLUGINCOM__ 1
+#define IUNKNOWN_C_GUTS \
+ void *_reserved; \
+ void *QueryInterface; \
+ void *AddRef; \
+ void *Release
+
+#include <CoreAudio/CoreAudioTypes.h>
+#include <CoreAudio/CoreAudio.h>
+#include <AudioToolbox/AudioToolbox.h>
+#include <AudioToolbox/AudioQueue.h>
+
+#include <freerdp/addin.h>
+#include <freerdp/channels/rdpsnd.h>
+
+#include "audin_main.h"
+
+#define MAC_AUDIO_QUEUE_NUM_BUFFERS 100
+
+/* Fix for #4462: Provide type alias if not declared (Mac OS < 10.10)
+ * https://developer.apple.com/documentation/coreaudio/audioformatid
+ */
+#ifndef AudioFormatID
+typedef UInt32 AudioFormatID;
+#endif
+
+#ifndef AudioFormatFlags
+typedef UInt32 AudioFormatFlags;
+#endif
+
+typedef struct
+{
+ IAudinDevice iface;
+
+ AUDIO_FORMAT format;
+ UINT32 FramesPerPacket;
+ int dev_unit;
+
+ AudinReceive receive;
+ void *user_data;
+
+ rdpContext *rdpcontext;
+
+ bool isAuthorized;
+ bool isOpen;
+ AudioQueueRef audioQueue;
+ AudioStreamBasicDescription audioFormat;
+ AudioQueueBufferRef audioBuffers[MAC_AUDIO_QUEUE_NUM_BUFFERS];
+} AudinMacDevice;
+
+static AudioFormatID audin_mac_get_format(const AUDIO_FORMAT *format)
+{
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ return kAudioFormatLinearPCM;
+
+ default:
+ return 0;
+ }
+}
+
+static AudioFormatFlags audin_mac_get_flags_for_format(const AUDIO_FORMAT *format)
+{
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ return kAudioFormatFlagIsSignedInteger;
+
+ default:
+ return 0;
+ }
+}
+
+static BOOL audin_mac_format_supported(IAudinDevice *device, const AUDIO_FORMAT *format)
+{
+ AudinMacDevice *mac = (AudinMacDevice *)device;
+ AudioFormatID req_fmt = 0;
+
+ if (!mac->isAuthorized)
+ return FALSE;
+
+ if (device == NULL || format == NULL)
+ return FALSE;
+
+ if (format->nChannels != 2)
+ return FALSE;
+
+ req_fmt = audin_mac_get_format(format);
+
+ if (req_fmt == 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_mac_set_format(IAudinDevice *device, const AUDIO_FORMAT *format,
+ UINT32 FramesPerPacket)
+{
+ AudinMacDevice *mac = (AudinMacDevice *)device;
+
+ if (!mac->isAuthorized)
+ return ERROR_INTERNAL_ERROR;
+
+ if (device == NULL || format == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ mac->FramesPerPacket = FramesPerPacket;
+ mac->format = *format;
+ WLog_INFO(TAG, "Audio Format %s [channels=%d, samples=%d, bits=%d]",
+ audio_format_get_tag_string(format->wFormatTag), format->nChannels,
+ format->nSamplesPerSec, format->wBitsPerSample);
+ mac->audioFormat.mBitsPerChannel = format->wBitsPerSample;
+
+ if (format->wBitsPerSample == 0)
+ mac->audioFormat.mBitsPerChannel = 16;
+
+ mac->audioFormat.mChannelsPerFrame = mac->format.nChannels;
+ mac->audioFormat.mFramesPerPacket = 1;
+
+ mac->audioFormat.mBytesPerFrame =
+ mac->audioFormat.mChannelsPerFrame * (mac->audioFormat.mBitsPerChannel / 8);
+ mac->audioFormat.mBytesPerPacket =
+ mac->audioFormat.mBytesPerFrame * mac->audioFormat.mFramesPerPacket;
+
+ mac->audioFormat.mFormatFlags = audin_mac_get_flags_for_format(format);
+ mac->audioFormat.mFormatID = audin_mac_get_format(format);
+ mac->audioFormat.mReserved = 0;
+ mac->audioFormat.mSampleRate = mac->format.nSamplesPerSec;
+ return CHANNEL_RC_OK;
+}
+
+static void mac_audio_queue_input_cb(void *aqData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
+ const AudioTimeStamp *inStartTime, UInt32 inNumPackets,
+ const AudioStreamPacketDescription *inPacketDesc)
+{
+ AudinMacDevice *mac = (AudinMacDevice *)aqData;
+ UINT error = CHANNEL_RC_OK;
+ const BYTE *buffer = inBuffer->mAudioData;
+ int buffer_size = inBuffer->mAudioDataByteSize;
+ (void)inAQ;
+ (void)inStartTime;
+ (void)inNumPackets;
+ (void)inPacketDesc;
+
+ if (buffer_size > 0)
+ error = mac->receive(&mac->format, buffer, buffer_size, mac->user_data);
+
+ AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "mac->receive failed with error %" PRIu32 "", error);
+ SetLastError(ERROR_INTERNAL_ERROR);
+ }
+}
+
+static UINT audin_mac_close(IAudinDevice *device)
+{
+ UINT errCode = CHANNEL_RC_OK;
+ char errString[1024];
+ OSStatus devStat;
+ AudinMacDevice *mac = (AudinMacDevice *)device;
+
+ if (!mac->isAuthorized)
+ return ERROR_INTERNAL_ERROR;
+
+ if (device == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if (mac->isOpen)
+ {
+ devStat = AudioQueueStop(mac->audioQueue, true);
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueStop failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ }
+
+ mac->isOpen = false;
+ }
+
+ if (mac->audioQueue)
+ {
+ devStat = AudioQueueDispose(mac->audioQueue, true);
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueDispose failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ }
+
+ mac->audioQueue = NULL;
+ }
+
+ mac->receive = NULL;
+ mac->user_data = NULL;
+ return errCode;
+}
+
+static UINT audin_mac_open(IAudinDevice *device, AudinReceive receive, void *user_data)
+{
+ AudinMacDevice *mac = (AudinMacDevice *)device;
+ DWORD errCode;
+ char errString[1024];
+ OSStatus devStat;
+
+ if (!mac->isAuthorized)
+ return ERROR_INTERNAL_ERROR;
+
+ mac->receive = receive;
+ mac->user_data = user_data;
+ devStat = AudioQueueNewInput(&(mac->audioFormat), mac_audio_queue_input_cb, mac, NULL,
+ kCFRunLoopCommonModes, 0, &(mac->audioQueue));
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueNewInput failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ goto err_out;
+ }
+
+ for (size_t index = 0; index < MAC_AUDIO_QUEUE_NUM_BUFFERS; index++)
+ {
+ devStat = AudioQueueAllocateBuffer(mac->audioQueue,
+ mac->FramesPerPacket * 2 * mac->format.nChannels,
+ &mac->audioBuffers[index]);
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueAllocateBuffer failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ goto err_out;
+ }
+
+ devStat = AudioQueueEnqueueBuffer(mac->audioQueue, mac->audioBuffers[index], 0, NULL);
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueEnqueueBuffer failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ goto err_out;
+ }
+ }
+
+ devStat = AudioQueueStart(mac->audioQueue, NULL);
+
+ if (devStat != 0)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "AudioQueueStart failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ goto err_out;
+ }
+
+ mac->isOpen = true;
+ return CHANNEL_RC_OK;
+err_out:
+ audin_mac_close(device);
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+}
+
+static UINT audin_mac_free(IAudinDevice *device)
+{
+ AudinMacDevice *mac = (AudinMacDevice *)device;
+ int error;
+
+ if (device == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = audin_mac_close(device)))
+ {
+ WLog_ERR(TAG, "audin_oss_close failed with error code %d!", error);
+ }
+
+ free(mac);
+ return CHANNEL_RC_OK;
+}
+
+static UINT audin_mac_parse_addin_args(AudinMacDevice *device, const ADDIN_ARGV *args)
+{
+ DWORD errCode;
+ char errString[1024];
+ int status;
+ char *str_num, *eptr;
+ DWORD flags;
+ const COMMAND_LINE_ARGUMENT_A *arg;
+ COMMAND_LINE_ARGUMENT_A audin_mac_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
+ NULL, NULL, -1, NULL, "audio device name" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
+
+ AudinMacDevice *mac = (AudinMacDevice *)device;
+
+ if (args->argc == 1)
+ return CHANNEL_RC_OK;
+
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status =
+ CommandLineParseArgumentsA(args->argc, args->argv, audin_mac_args, flags, mac, NULL, NULL);
+
+ if (status < 0)
+ return ERROR_INVALID_PARAMETER;
+
+ arg = audin_mac_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
+ {
+ str_num = _strdup(arg->Value);
+
+ if (!str_num)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "_strdup failed with %s [%d]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ mac->dev_unit = strtol(str_num, &eptr, 10);
+
+ if (mac->dev_unit < 0 || *eptr != '\0')
+ mac->dev_unit = -1;
+
+ free(str_num);
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+FREERDP_ENTRY_POINT(
+ UINT mac_freerdp_audin_client_subsystem_entry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ DWORD errCode;
+ char errString[1024];
+ const ADDIN_ARGV *args;
+ AudinMacDevice *mac;
+ UINT error;
+ mac = (AudinMacDevice *)calloc(1, sizeof(AudinMacDevice));
+
+ if (!mac)
+ {
+ errCode = GetLastError();
+ WLog_ERR(TAG, "calloc failed with %s [%" PRIu32 "]",
+ winpr_strerror(errCode, errString, sizeof(errString)), errCode);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ mac->iface.Open = audin_mac_open;
+ mac->iface.FormatSupported = audin_mac_format_supported;
+ mac->iface.SetFormat = audin_mac_set_format;
+ mac->iface.Close = audin_mac_close;
+ mac->iface.Free = audin_mac_free;
+ mac->rdpcontext = pEntryPoints->rdpcontext;
+ mac->dev_unit = -1;
+ args = pEntryPoints->args;
+
+ if ((error = audin_mac_parse_addin_args(mac, args)))
+ {
+ WLog_ERR(TAG, "audin_mac_parse_addin_args failed with %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice *)mac)))
+ {
+ WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+#if defined(MAC_OS_X_VERSION_10_14)
+ if (@available(macOS 10.14, *))
+ {
+ @autoreleasepool
+ {
+ AVAuthorizationStatus status =
+ [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
+ switch (status)
+ {
+ case AVAuthorizationStatusAuthorized:
+ mac->isAuthorized = TRUE;
+ break;
+ case AVAuthorizationStatusNotDetermined:
+ [AVCaptureDevice
+ requestAccessForMediaType:AVMediaTypeAudio
+ completionHandler:^(BOOL granted) {
+ if (granted == YES)
+ {
+ mac->isAuthorized = TRUE;
+ }
+ else
+ WLog_WARN(TAG, "Microphone access denied by user");
+ }];
+ break;
+ case AVAuthorizationStatusRestricted:
+ WLog_WARN(TAG, "Microphone access restricted by policy");
+ break;
+ case AVAuthorizationStatusDenied:
+ WLog_WARN(TAG, "Microphone access denied by policy");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+#endif
+
+ return CHANNEL_RC_OK;
+error_out:
+ free(mac);
+ return error;
+}
diff --git a/channels/audin/client/opensles/CMakeLists.txt b/channels/audin/client/opensles/CMakeLists.txt
new file mode 100644
index 0000000..9b2a287
--- /dev/null
+++ b/channels/audin/client/opensles/CMakeLists.txt
@@ -0,0 +1,36 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2013 Armin Novak <armin.novak@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("audin" "opensles" "")
+
+find_package(OpenSLES REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ opensl_io.c
+ audin_opensl_es.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${OpenSLES_LIBRARIES}
+)
+
+include_directories(..)
+include_directories(${OpenSLES_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/audin/client/opensles/audin_opensl_es.c b/channels/audin/client/opensles/audin_opensl_es.c
new file mode 100644
index 0000000..a59fe05
--- /dev/null
+++ b/channels/audin/client/opensles/audin_opensl_es.c
@@ -0,0 +1,336 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel - OpenSL ES implementation
+ *
+ * Copyright 2013 Armin Novak <armin.novak@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/cmdline.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/rdpsnd.h>
+
+#include <SLES/OpenSLES.h>
+#include <freerdp/client/audin.h>
+
+#include "audin_main.h"
+#include "opensl_io.h"
+
+typedef struct
+{
+ IAudinDevice iface;
+
+ char* device_name;
+ OPENSL_STREAM* stream;
+
+ AUDIO_FORMAT format;
+ UINT32 frames_per_packet;
+
+ UINT32 bytes_per_channel;
+
+ AudinReceive receive;
+
+ void* user_data;
+
+ rdpContext* rdpcontext;
+ wLog* log;
+} AudinOpenSLESDevice;
+
+static UINT audin_opensles_close(IAudinDevice* device);
+
+static void audin_receive(void* context, const void* data, size_t size)
+{
+ UINT error;
+ AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)context;
+
+ if (!opensles || !data)
+ {
+ WLog_ERR(TAG, "Invalid arguments context=%p, data=%p", opensles, data);
+ return;
+ }
+
+ error = opensles->receive(&opensles->format, data, size, opensles->user_data);
+
+ if (error && opensles->rdpcontext)
+ setChannelError(opensles->rdpcontext, error, "audin_receive reported an error");
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_opensles_free(IAudinDevice* device)
+{
+ AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device;
+
+ if (!opensles)
+ return ERROR_INVALID_PARAMETER;
+
+ WLog_Print(opensles->log, WLOG_DEBUG, "device=%p", (void*)device);
+
+ free(opensles->device_name);
+ free(opensles);
+ return CHANNEL_RC_OK;
+}
+
+static BOOL audin_opensles_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
+{
+ AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device;
+
+ if (!opensles || !format)
+ return FALSE;
+
+ WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, format=%p", (void*)opensles, (void*)format);
+ WINPR_ASSERT(format);
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM: /* PCM */
+ if (format->cbSize == 0 && (format->nSamplesPerSec <= 48000) &&
+ (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
+ (format->nChannels >= 1 && format->nChannels <= 2))
+ {
+ return TRUE;
+ }
+
+ break;
+
+ default:
+ WLog_Print(opensles->log, WLOG_DEBUG, "Encoding '%s' [0x%04" PRIX16 "] not supported",
+ audio_format_get_tag_string(format->wFormatTag), format->wFormatTag);
+ break;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_opensles_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
+ UINT32 FramesPerPacket)
+{
+ AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device;
+
+ if (!opensles || !format)
+ return ERROR_INVALID_PARAMETER;
+
+ WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, format=%p, FramesPerPacket=%" PRIu32 "",
+ (void*)device, (void*)format, FramesPerPacket);
+ WINPR_ASSERT(format);
+
+ opensles->format = *format;
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ opensles->frames_per_packet = FramesPerPacket;
+
+ switch (format->wBitsPerSample)
+ {
+ case 4:
+ opensles->bytes_per_channel = 1;
+ break;
+
+ case 8:
+ opensles->bytes_per_channel = 1;
+ break;
+
+ case 16:
+ opensles->bytes_per_channel = 2;
+ break;
+
+ default:
+ return ERROR_UNSUPPORTED_TYPE;
+ }
+
+ break;
+
+ default:
+ WLog_Print(opensles->log, WLOG_ERROR,
+ "Encoding '%" PRIu16 "' [%04" PRIX16 "] not supported", format->wFormatTag,
+ format->wFormatTag);
+ return ERROR_UNSUPPORTED_TYPE;
+ }
+
+ WLog_Print(opensles->log, WLOG_DEBUG, "frames_per_packet=%" PRIu32,
+ opensles->frames_per_packet);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_opensles_open(IAudinDevice* device, AudinReceive receive, void* user_data)
+{
+ AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device;
+
+ if (!opensles || !receive || !user_data)
+ return ERROR_INVALID_PARAMETER;
+
+ WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, receive=%p, user_data=%p", (void*)device,
+ (void*)receive, (void*)user_data);
+
+ if (opensles->stream)
+ goto error_out;
+
+ if (!(opensles->stream = android_OpenRecDevice(
+ opensles, audin_receive, opensles->format.nSamplesPerSec, opensles->format.nChannels,
+ opensles->frames_per_packet, opensles->format.wBitsPerSample)))
+ {
+ WLog_Print(opensles->log, WLOG_ERROR, "android_OpenRecDevice failed!");
+ goto error_out;
+ }
+
+ opensles->receive = receive;
+ opensles->user_data = user_data;
+ return CHANNEL_RC_OK;
+error_out:
+ audin_opensles_close(device);
+ return ERROR_INTERNAL_ERROR;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT audin_opensles_close(IAudinDevice* device)
+{
+ AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device;
+
+ if (!opensles)
+ return ERROR_INVALID_PARAMETER;
+
+ WLog_Print(opensles->log, WLOG_DEBUG, "device=%p", (void*)device);
+ android_CloseRecDevice(opensles->stream);
+ opensles->receive = NULL;
+ opensles->user_data = NULL;
+ opensles->stream = NULL;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_opensles_parse_addin_args(AudinOpenSLESDevice* device, const ADDIN_ARGV* args)
+{
+ UINT status;
+ DWORD flags;
+ const COMMAND_LINE_ARGUMENT_A* arg;
+ AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device;
+ COMMAND_LINE_ARGUMENT_A audin_opensles_args[] = {
+ { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", NULL, NULL, -1, NULL,
+ "audio device name" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
+ };
+
+ WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, args=%p", (void*)device, (void*)args);
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status = CommandLineParseArgumentsA(args->argc, args->argv, audin_opensles_args, flags,
+ opensles, NULL, NULL);
+
+ if (status < 0)
+ return status;
+
+ arg = audin_opensles_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
+ {
+ opensles->device_name = _strdup(arg->Value);
+
+ if (!opensles->device_name)
+ {
+ WLog_Print(opensles->log, WLOG_ERROR, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT opensles_freerdp_audin_client_subsystem_entry(
+ PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ const ADDIN_ARGV* args;
+ AudinOpenSLESDevice* opensles;
+ UINT error;
+ opensles = (AudinOpenSLESDevice*)calloc(1, sizeof(AudinOpenSLESDevice));
+
+ if (!opensles)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ opensles->log = WLog_Get(TAG);
+ opensles->iface.Open = audin_opensles_open;
+ opensles->iface.FormatSupported = audin_opensles_format_supported;
+ opensles->iface.SetFormat = audin_opensles_set_format;
+ opensles->iface.Close = audin_opensles_close;
+ opensles->iface.Free = audin_opensles_free;
+ opensles->rdpcontext = pEntryPoints->rdpcontext;
+ args = pEntryPoints->args;
+
+ if ((error = audin_opensles_parse_addin_args(opensles, args)))
+ {
+ WLog_Print(opensles->log, WLOG_ERROR,
+ "audin_opensles_parse_addin_args failed with errorcode %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)opensles)))
+ {
+ WLog_Print(opensles->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!",
+ error);
+ goto error_out;
+ }
+
+ return CHANNEL_RC_OK;
+error_out:
+ free(opensles);
+ return error;
+}
diff --git a/channels/audin/client/opensles/opensl_io.c b/channels/audin/client/opensles/opensl_io.c
new file mode 100644
index 0000000..39b3dc8
--- /dev/null
+++ b/channels/audin/client/opensles/opensl_io.c
@@ -0,0 +1,388 @@
+/*
+opensl_io.c:
+Android OpenSL input/output module
+Copyright (c) 2012, Victor Lazzarini
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the <organization> nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <winpr/assert.h>
+
+#include "audin_main.h"
+#include "opensl_io.h"
+#define CONV16BIT 32768
+#define CONVMYFLT (1. / 32768.)
+
+typedef struct
+{
+ size_t size;
+ void* data;
+} queue_element;
+
+struct opensl_stream
+{
+ // engine interfaces
+ SLObjectItf engineObject;
+ SLEngineItf engineEngine;
+
+ // device interfaces
+ SLDeviceVolumeItf deviceVolume;
+
+ // recorder interfaces
+ SLObjectItf recorderObject;
+ SLRecordItf recorderRecord;
+ SLAndroidSimpleBufferQueueItf recorderBufferQueue;
+
+ unsigned int inchannels;
+ unsigned int sr;
+ unsigned int buffersize;
+ unsigned int bits_per_sample;
+
+ queue_element* prep;
+ queue_element* next;
+
+ void* context;
+ opensl_receive_t receive;
+};
+
+static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void* context);
+
+// creates the OpenSL ES audio engine
+static SLresult openSLCreateEngine(OPENSL_STREAM* p)
+{
+ SLresult result;
+ // create engine
+ result = slCreateEngine(&(p->engineObject), 0, NULL, 0, NULL, NULL);
+
+ if (result != SL_RESULT_SUCCESS)
+ goto engine_end;
+
+ // realize the engine
+ result = (*p->engineObject)->Realize(p->engineObject, SL_BOOLEAN_FALSE);
+
+ if (result != SL_RESULT_SUCCESS)
+ goto engine_end;
+
+ // get the engine interface, which is needed in order to create other objects
+ result = (*p->engineObject)->GetInterface(p->engineObject, SL_IID_ENGINE, &(p->engineEngine));
+
+ if (result != SL_RESULT_SUCCESS)
+ goto engine_end;
+
+ // get the volume interface - important, this is optional!
+ result =
+ (*p->engineObject)->GetInterface(p->engineObject, SL_IID_DEVICEVOLUME, &(p->deviceVolume));
+
+ if (result != SL_RESULT_SUCCESS)
+ {
+ p->deviceVolume = NULL;
+ result = SL_RESULT_SUCCESS;
+ }
+
+engine_end:
+ WINPR_ASSERT(SL_RESULT_SUCCESS == result);
+ return result;
+}
+
+// Open the OpenSL ES device for input
+static SLresult openSLRecOpen(OPENSL_STREAM* p)
+{
+ SLresult result;
+ SLuint32 sr = p->sr;
+ SLuint32 channels = p->inchannels;
+ WINPR_ASSERT(!p->recorderObject);
+
+ if (channels)
+ {
+ switch (sr)
+ {
+ case 8000:
+ sr = SL_SAMPLINGRATE_8;
+ break;
+
+ case 11025:
+ sr = SL_SAMPLINGRATE_11_025;
+ break;
+
+ case 16000:
+ sr = SL_SAMPLINGRATE_16;
+ break;
+
+ case 22050:
+ sr = SL_SAMPLINGRATE_22_05;
+ break;
+
+ case 24000:
+ sr = SL_SAMPLINGRATE_24;
+ break;
+
+ case 32000:
+ sr = SL_SAMPLINGRATE_32;
+ break;
+
+ case 44100:
+ sr = SL_SAMPLINGRATE_44_1;
+ break;
+
+ case 48000:
+ sr = SL_SAMPLINGRATE_48;
+ break;
+
+ case 64000:
+ sr = SL_SAMPLINGRATE_64;
+ break;
+
+ case 88200:
+ sr = SL_SAMPLINGRATE_88_2;
+ break;
+
+ case 96000:
+ sr = SL_SAMPLINGRATE_96;
+ break;
+
+ case 192000:
+ sr = SL_SAMPLINGRATE_192;
+ break;
+
+ default:
+ return -1;
+ }
+
+ // configure audio source
+ SLDataLocator_IODevice loc_dev = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT,
+ SL_DEFAULTDEVICEID_AUDIOINPUT, NULL };
+ SLDataSource audioSrc = { &loc_dev, NULL };
+ // configure audio sink
+ int speakers;
+
+ if (channels > 1)
+ speakers = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+ else
+ speakers = SL_SPEAKER_FRONT_CENTER;
+
+ SLDataLocator_AndroidSimpleBufferQueue loc_bq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
+ 2 };
+ SLDataFormat_PCM format_pcm;
+ format_pcm.formatType = SL_DATAFORMAT_PCM;
+ format_pcm.numChannels = channels;
+ format_pcm.samplesPerSec = sr;
+ format_pcm.channelMask = speakers;
+ format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
+
+ if (16 == p->bits_per_sample)
+ {
+ format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format_pcm.containerSize = 16;
+ }
+ else if (8 == p->bits_per_sample)
+ {
+ format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_8;
+ format_pcm.containerSize = 8;
+ }
+ else
+ WINPR_ASSERT(0);
+
+ SLDataSink audioSnk = { &loc_bq, &format_pcm };
+ // create audio recorder
+ // (requires the RECORD_AUDIO permission)
+ const SLInterfaceID id[] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
+ const SLboolean req[] = { SL_BOOLEAN_TRUE };
+ result = (*p->engineEngine)
+ ->CreateAudioRecorder(p->engineEngine, &(p->recorderObject), &audioSrc,
+ &audioSnk, 1, id, req);
+ WINPR_ASSERT(!result);
+
+ if (SL_RESULT_SUCCESS != result)
+ goto end_recopen;
+
+ // realize the audio recorder
+ result = (*p->recorderObject)->Realize(p->recorderObject, SL_BOOLEAN_FALSE);
+ WINPR_ASSERT(!result);
+
+ if (SL_RESULT_SUCCESS != result)
+ goto end_recopen;
+
+ // get the record interface
+ result = (*p->recorderObject)
+ ->GetInterface(p->recorderObject, SL_IID_RECORD, &(p->recorderRecord));
+ WINPR_ASSERT(!result);
+
+ if (SL_RESULT_SUCCESS != result)
+ goto end_recopen;
+
+ // get the buffer queue interface
+ result = (*p->recorderObject)
+ ->GetInterface(p->recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ &(p->recorderBufferQueue));
+ WINPR_ASSERT(!result);
+
+ if (SL_RESULT_SUCCESS != result)
+ goto end_recopen;
+
+ // register callback on the buffer queue
+ result = (*p->recorderBufferQueue)
+ ->RegisterCallback(p->recorderBufferQueue, bqRecorderCallback, p);
+ WINPR_ASSERT(!result);
+
+ if (SL_RESULT_SUCCESS != result)
+ goto end_recopen;
+
+ end_recopen:
+ return result;
+ }
+ else
+ return SL_RESULT_SUCCESS;
+}
+
+// close the OpenSL IO and destroy the audio engine
+static void openSLDestroyEngine(OPENSL_STREAM* p)
+{
+ // destroy audio recorder object, and invalidate all associated interfaces
+ if (p->recorderObject != NULL)
+ {
+ (*p->recorderObject)->Destroy(p->recorderObject);
+ p->recorderObject = NULL;
+ p->recorderRecord = NULL;
+ p->recorderBufferQueue = NULL;
+ }
+
+ // destroy engine object, and invalidate all associated interfaces
+ if (p->engineObject != NULL)
+ {
+ (*p->engineObject)->Destroy(p->engineObject);
+ p->engineObject = NULL;
+ p->engineEngine = NULL;
+ }
+}
+
+static queue_element* opensles_queue_element_new(size_t size)
+{
+ queue_element* q = calloc(1, sizeof(queue_element));
+
+ if (!q)
+ goto fail;
+
+ q->size = size;
+ q->data = malloc(size);
+
+ if (!q->data)
+ goto fail;
+
+ return q;
+fail:
+ free(q);
+ return NULL;
+}
+
+static void opensles_queue_element_free(void* obj)
+{
+ queue_element* e = (queue_element*)obj;
+
+ if (e)
+ free(e->data);
+
+ free(e);
+}
+
+// open the android audio device for input
+OPENSL_STREAM* android_OpenRecDevice(void* context, opensl_receive_t receive, int sr,
+ int inchannels, int bufferframes, int bits_per_sample)
+{
+ OPENSL_STREAM* p;
+
+ if (!context || !receive)
+ return NULL;
+
+ p = (OPENSL_STREAM*)calloc(1, sizeof(OPENSL_STREAM));
+
+ if (!p)
+ return NULL;
+
+ p->context = context;
+ p->receive = receive;
+ p->inchannels = inchannels;
+ p->sr = sr;
+ p->buffersize = bufferframes;
+ p->bits_per_sample = bits_per_sample;
+
+ if ((p->bits_per_sample != 8) && (p->bits_per_sample != 16))
+ goto fail;
+
+ if (openSLCreateEngine(p) != SL_RESULT_SUCCESS)
+ goto fail;
+
+ if (openSLRecOpen(p) != SL_RESULT_SUCCESS)
+ goto fail;
+
+ /* Create receive buffers, prepare them and start recording */
+ p->prep = opensles_queue_element_new(p->buffersize * p->bits_per_sample / 8);
+ p->next = opensles_queue_element_new(p->buffersize * p->bits_per_sample / 8);
+
+ if (!p->prep || !p->next)
+ goto fail;
+
+ (*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue, p->next->data, p->next->size);
+ (*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue, p->prep->data, p->prep->size);
+ (*p->recorderRecord)->SetRecordState(p->recorderRecord, SL_RECORDSTATE_RECORDING);
+ return p;
+fail:
+ android_CloseRecDevice(p);
+ return NULL;
+}
+
+// close the android audio device
+void android_CloseRecDevice(OPENSL_STREAM* p)
+{
+ if (p == NULL)
+ return;
+
+ opensles_queue_element_free(p->next);
+ opensles_queue_element_free(p->prep);
+ openSLDestroyEngine(p);
+ free(p);
+}
+
+// this callback handler is called every time a buffer finishes recording
+static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
+{
+ OPENSL_STREAM* p = (OPENSL_STREAM*)context;
+ queue_element* e;
+
+ if (!p)
+ return;
+
+ e = p->next;
+
+ if (!e)
+ return;
+
+ if (!p->context || !p->receive)
+ WLog_WARN(TAG, "Missing receive callback=%p, context=%p", p->receive, p->context);
+ else
+ p->receive(p->context, e->data, e->size);
+
+ p->next = p->prep;
+ p->prep = e;
+ (*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue, e->data, e->size);
+}
diff --git a/channels/audin/client/opensles/opensl_io.h b/channels/audin/client/opensles/opensl_io.h
new file mode 100644
index 0000000..e99522c
--- /dev/null
+++ b/channels/audin/client/opensles/opensl_io.h
@@ -0,0 +1,65 @@
+/*
+opensl_io.c:
+Android OpenSL input/output module header
+Copyright (c) 2012, Victor Lazzarini
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the <organization> nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef FREERDP_CHANNEL_AUDIN_CLIENT_OPENSL_IO_H
+#define FREERDP_CHANNEL_AUDIN_CLIENT_OPENSL_IO_H
+
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+
+#include <freerdp/api.h>
+
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct opensl_stream OPENSL_STREAM;
+
+ typedef void (*opensl_receive_t)(void* context, const void* data, size_t size);
+
+ /*
+ Open the audio device with a given sampling rate (sr), input and output channels and IO buffer
+ size in frames. Returns a handle to the OpenSL stream
+ */
+ FREERDP_LOCAL OPENSL_STREAM* android_OpenRecDevice(void* context, opensl_receive_t receive,
+ int sr, int inchannels, int bufferframes,
+ int bits_per_sample);
+ /*
+ Close the audio device
+ */
+ FREERDP_LOCAL void android_CloseRecDevice(OPENSL_STREAM* p);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif /* FREERDP_CHANNEL_AUDIN_CLIENT_OPENSL_IO_H */
diff --git a/channels/audin/client/oss/CMakeLists.txt b/channels/audin/client/oss/CMakeLists.txt
new file mode 100644
index 0000000..6b747e4
--- /dev/null
+++ b/channels/audin/client/oss/CMakeLists.txt
@@ -0,0 +1,36 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("audin" "oss" "")
+
+find_package(OSS REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ audin_oss.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${OSS_LIBRARIES}
+)
+
+include_directories(..)
+include_directories(${OSS_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
+
diff --git a/channels/audin/client/oss/audin_oss.c b/channels/audin/client/oss/audin_oss.c
new file mode 100644
index 0000000..979a800
--- /dev/null
+++ b/channels/audin/client/oss/audin_oss.c
@@ -0,0 +1,491 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel - OSS implementation
+ *
+ * Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/string.h>
+#include <winpr/thread.h>
+#include <winpr/cmdline.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <unistd.h>
+#if defined(__OpenBSD__)
+#include <soundcard.h>
+#else
+#include <sys/soundcard.h>
+#endif
+#include <sys/ioctl.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/rdpsnd.h>
+
+#include "audin_main.h"
+
+typedef struct
+{
+ IAudinDevice iface;
+
+ HANDLE thread;
+ HANDLE stopEvent;
+
+ AUDIO_FORMAT format;
+ UINT32 FramesPerPacket;
+ int dev_unit;
+
+ AudinReceive receive;
+ void* user_data;
+
+ rdpContext* rdpcontext;
+} AudinOSSDevice;
+
+#define OSS_LOG_ERR(_text, _error) \
+ do \
+ { \
+ if (_error != 0) \
+ { \
+ char buffer[256] = { 0 }; \
+ WLog_ERR(TAG, "%s: %i - %s\n", _text, _error, \
+ winpr_strerror(_error, buffer, sizeof(buffer))); \
+ } \
+ } while (0)
+
+static UINT32 audin_oss_get_format(const AUDIO_FORMAT* format)
+{
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ switch (format->wBitsPerSample)
+ {
+ case 8:
+ return AFMT_S8;
+
+ case 16:
+ return AFMT_S16_LE;
+ }
+
+ break;
+
+ case WAVE_FORMAT_ALAW:
+ return AFMT_A_LAW;
+
+ case WAVE_FORMAT_MULAW:
+ return AFMT_MU_LAW;
+ }
+
+ return 0;
+}
+
+static BOOL audin_oss_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
+{
+ if (device == NULL || format == NULL)
+ return FALSE;
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ if (format->cbSize != 0 || format->nSamplesPerSec > 48000 ||
+ (format->wBitsPerSample != 8 && format->wBitsPerSample != 16) ||
+ (format->nChannels != 1 && format->nChannels != 2))
+ return FALSE;
+
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_oss_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
+ UINT32 FramesPerPacket)
+{
+ AudinOSSDevice* oss = (AudinOSSDevice*)device;
+
+ if (device == NULL || format == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ oss->FramesPerPacket = FramesPerPacket;
+ oss->format = *format;
+ return CHANNEL_RC_OK;
+}
+
+static DWORD WINAPI audin_oss_thread_func(LPVOID arg)
+{
+ char dev_name[PATH_MAX] = "/dev/dsp";
+ char mixer_name[PATH_MAX] = "/dev/mixer";
+ int pcm_handle = -1;
+ int mixer_handle = 0;
+ BYTE* buffer = NULL;
+ unsigned long tmp = 0;
+ size_t buffer_size = 0;
+ AudinOSSDevice* oss = (AudinOSSDevice*)arg;
+ UINT error = 0;
+ DWORD status = 0;
+
+ if (oss == NULL)
+ {
+ error = ERROR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ if (oss->dev_unit != -1)
+ {
+ sprintf_s(dev_name, (PATH_MAX - 1), "/dev/dsp%i", oss->dev_unit);
+ sprintf_s(mixer_name, PATH_MAX - 1, "/dev/mixer%i", oss->dev_unit);
+ }
+
+ WLog_INFO(TAG, "open: %s", dev_name);
+
+ if ((pcm_handle = open(dev_name, O_RDONLY)) < 0)
+ {
+ OSS_LOG_ERR("sound dev open failed", errno);
+ error = ERROR_INTERNAL_ERROR;
+ goto err_out;
+ }
+
+ /* Set rec volume to 100%. */
+ if ((mixer_handle = open(mixer_name, O_RDWR)) < 0)
+ {
+ OSS_LOG_ERR("mixer open failed, not critical", errno);
+ }
+ else
+ {
+ tmp = (100 | (100 << 8));
+
+ if (ioctl(mixer_handle, MIXER_WRITE(SOUND_MIXER_MIC), &tmp) == -1)
+ OSS_LOG_ERR("WRITE_MIXER - SOUND_MIXER_MIC, not critical", errno);
+
+ tmp = (100 | (100 << 8));
+
+ if (ioctl(mixer_handle, MIXER_WRITE(SOUND_MIXER_RECLEV), &tmp) == -1)
+ OSS_LOG_ERR("WRITE_MIXER - SOUND_MIXER_RECLEV, not critical", errno);
+
+ close(mixer_handle);
+ }
+
+#if 0 /* FreeBSD OSS implementation at this moment (2015.03) does not set PCM_CAP_INPUT flag. */
+ tmp = 0;
+
+ if (ioctl(pcm_handle, SNDCTL_DSP_GETCAPS, &tmp) == -1)
+ {
+ OSS_LOG_ERR("SNDCTL_DSP_GETCAPS failed, try ignory", errno);
+ }
+ else if ((tmp & PCM_CAP_INPUT) == 0)
+ {
+ OSS_LOG_ERR("Device does not supports playback", EOPNOTSUPP);
+ goto err_out;
+ }
+
+#endif
+ /* Set format. */
+ tmp = audin_oss_get_format(&oss->format);
+
+ if (ioctl(pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1)
+ OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed", errno);
+
+ tmp = oss->format.nChannels;
+
+ if (ioctl(pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1)
+ OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno);
+
+ tmp = oss->format.nSamplesPerSec;
+
+ if (ioctl(pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1)
+ OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno);
+
+ tmp = oss->format.nBlockAlign;
+
+ if (ioctl(pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
+ OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed", errno);
+
+ buffer_size = (oss->FramesPerPacket * oss->format.nChannels * (oss->format.wBitsPerSample / 8));
+ buffer = (BYTE*)calloc((buffer_size + sizeof(void*)), sizeof(BYTE));
+
+ if (NULL == buffer)
+ {
+ OSS_LOG_ERR("malloc() fail", errno);
+ error = ERROR_NOT_ENOUGH_MEMORY;
+ goto err_out;
+ }
+
+ while (1)
+ {
+ SSIZE_T stmp = -1;
+ status = WaitForSingleObject(oss->stopEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ goto err_out;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ stmp = read(pcm_handle, buffer, buffer_size);
+
+ /* Error happen. */
+ if (stmp < 0)
+ {
+ OSS_LOG_ERR("read() error", errno);
+ continue;
+ }
+
+ if ((size_t)stmp < buffer_size) /* Not enouth data. */
+ continue;
+
+ if ((error = oss->receive(&oss->format, buffer, buffer_size, oss->user_data)))
+ {
+ WLog_ERR(TAG, "oss->receive failed with error %" PRIu32 "", error);
+ break;
+ }
+ }
+
+err_out:
+
+ if (error && oss && oss->rdpcontext)
+ setChannelError(oss->rdpcontext, error, "audin_oss_thread_func reported an error");
+
+ if (pcm_handle != -1)
+ {
+ WLog_INFO(TAG, "close: %s", dev_name);
+ close(pcm_handle);
+ }
+
+ free(buffer);
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_oss_open(IAudinDevice* device, AudinReceive receive, void* user_data)
+{
+ AudinOSSDevice* oss = (AudinOSSDevice*)device;
+ oss->receive = receive;
+ oss->user_data = user_data;
+
+ if (!(oss->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!(oss->thread = CreateThread(NULL, 0, audin_oss_thread_func, oss, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ CloseHandle(oss->stopEvent);
+ oss->stopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_oss_close(IAudinDevice* device)
+{
+ UINT error = 0;
+ AudinOSSDevice* oss = (AudinOSSDevice*)device;
+
+ if (device == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if (oss->stopEvent != NULL)
+ {
+ SetEvent(oss->stopEvent);
+
+ if (WaitForSingleObject(oss->thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ CloseHandle(oss->stopEvent);
+ oss->stopEvent = NULL;
+ CloseHandle(oss->thread);
+ oss->thread = NULL;
+ }
+
+ oss->receive = NULL;
+ oss->user_data = NULL;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_oss_free(IAudinDevice* device)
+{
+ AudinOSSDevice* oss = (AudinOSSDevice*)device;
+ UINT error = 0;
+
+ if (device == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = audin_oss_close(device)))
+ {
+ WLog_ERR(TAG, "audin_oss_close failed with error code %" PRIu32 "!", error);
+ }
+
+ free(oss);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_oss_parse_addin_args(AudinOSSDevice* device, const ADDIN_ARGV* args)
+{
+ int status = 0;
+ char* str_num = NULL;
+ char* eptr = NULL;
+ DWORD flags = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ AudinOSSDevice* oss = (AudinOSSDevice*)device;
+ COMMAND_LINE_ARGUMENT_A audin_oss_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
+ NULL, NULL, -1, NULL, "audio device name" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
+
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status =
+ CommandLineParseArgumentsA(args->argc, args->argv, audin_oss_args, flags, oss, NULL, NULL);
+
+ if (status < 0)
+ return ERROR_INVALID_PARAMETER;
+
+ arg = audin_oss_args;
+ errno = 0;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
+ {
+ str_num = _strdup(arg->Value);
+
+ if (!str_num)
+ {
+ WLog_ERR(TAG, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ {
+ long val = strtol(str_num, &eptr, 10);
+
+ if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
+ {
+ free(str_num);
+ return CHANNEL_RC_NULL_DATA;
+ }
+
+ oss->dev_unit = (INT32)val;
+ }
+
+ if (oss->dev_unit < 0 || *eptr != '\0')
+ oss->dev_unit = -1;
+
+ free(str_num);
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(
+ UINT oss_freerdp_audin_client_subsystem_entry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ const ADDIN_ARGV* args = NULL;
+ AudinOSSDevice* oss = NULL;
+ UINT error = 0;
+ oss = (AudinOSSDevice*)calloc(1, sizeof(AudinOSSDevice));
+
+ if (!oss)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ oss->iface.Open = audin_oss_open;
+ oss->iface.FormatSupported = audin_oss_format_supported;
+ oss->iface.SetFormat = audin_oss_set_format;
+ oss->iface.Close = audin_oss_close;
+ oss->iface.Free = audin_oss_free;
+ oss->rdpcontext = pEntryPoints->rdpcontext;
+ oss->dev_unit = -1;
+ args = pEntryPoints->args;
+
+ if ((error = audin_oss_parse_addin_args(oss, args)))
+ {
+ WLog_ERR(TAG, "audin_oss_parse_addin_args failed with errorcode %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)oss)))
+ {
+ WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ return CHANNEL_RC_OK;
+error_out:
+ free(oss);
+ return error;
+}
diff --git a/channels/audin/client/pulse/CMakeLists.txt b/channels/audin/client/pulse/CMakeLists.txt
new file mode 100644
index 0000000..5bf0fcd
--- /dev/null
+++ b/channels/audin/client/pulse/CMakeLists.txt
@@ -0,0 +1,35 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("audin" "pulse" "")
+
+find_package(PulseAudio REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ audin_pulse.c)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${PULSEAUDIO_LIBRARY}
+ ${PULSEAUDIO_MAINLOOP_LIBRARY}
+)
+
+include_directories(..)
+include_directories(${PULSEAUDIO_INCLUDE_DIR})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/audin/client/pulse/audin_pulse.c b/channels/audin/client/pulse/audin_pulse.c
new file mode 100644
index 0000000..0e330ad
--- /dev/null
+++ b/channels/audin/client/pulse/audin_pulse.c
@@ -0,0 +1,566 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel - PulseAudio implementation
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/cmdline.h>
+#include <winpr/wlog.h>
+
+#include <pulse/pulseaudio.h>
+
+#include <freerdp/types.h>
+#include <freerdp/addin.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/codec/audio.h>
+#include <freerdp/client/audin.h>
+
+#include "audin_main.h"
+
+typedef struct
+{
+ IAudinDevice iface;
+
+ char* device_name;
+ UINT32 frames_per_packet;
+ pa_threaded_mainloop* mainloop;
+ pa_context* context;
+ pa_sample_spec sample_spec;
+ pa_stream* stream;
+ AUDIO_FORMAT format;
+
+ size_t bytes_per_frame;
+ size_t buffer_frames;
+
+ AudinReceive receive;
+ void* user_data;
+
+ rdpContext* rdpcontext;
+ wLog* log;
+} AudinPulseDevice;
+
+static const char* pulse_context_state_string(pa_context_state_t state)
+{
+ switch (state)
+ {
+ case PA_CONTEXT_UNCONNECTED:
+ return "PA_CONTEXT_UNCONNECTED";
+ case PA_CONTEXT_CONNECTING:
+ return "PA_CONTEXT_CONNECTING";
+ case PA_CONTEXT_AUTHORIZING:
+ return "PA_CONTEXT_AUTHORIZING";
+ case PA_CONTEXT_SETTING_NAME:
+ return "PA_CONTEXT_SETTING_NAME";
+ case PA_CONTEXT_READY:
+ return "PA_CONTEXT_READY";
+ case PA_CONTEXT_FAILED:
+ return "PA_CONTEXT_FAILED";
+ case PA_CONTEXT_TERMINATED:
+ return "PA_CONTEXT_TERMINATED";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static const char* pulse_stream_state_string(pa_stream_state_t state)
+{
+ switch (state)
+ {
+ case PA_STREAM_UNCONNECTED:
+ return "PA_STREAM_UNCONNECTED";
+ case PA_STREAM_CREATING:
+ return "PA_STREAM_CREATING";
+ case PA_STREAM_READY:
+ return "PA_STREAM_READY";
+ case PA_STREAM_FAILED:
+ return "PA_STREAM_FAILED";
+ case PA_STREAM_TERMINATED:
+ return "PA_STREAM_TERMINATED";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static void audin_pulse_context_state_callback(pa_context* context, void* userdata)
+{
+ AudinPulseDevice* pulse = (AudinPulseDevice*)userdata;
+ pa_context_state_t state = pa_context_get_state(context);
+
+ WLog_Print(pulse->log, WLOG_DEBUG, "context state %s", pulse_context_state_string(state));
+ switch (state)
+ {
+ case PA_CONTEXT_READY:
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+ break;
+
+ case PA_CONTEXT_FAILED:
+ case PA_CONTEXT_TERMINATED:
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_pulse_connect(IAudinDevice* device)
+{
+ pa_context_state_t state = PA_CONTEXT_FAILED;
+ AudinPulseDevice* pulse = (AudinPulseDevice*)device;
+
+ if (!pulse->context)
+ return ERROR_INVALID_PARAMETER;
+
+ if (pa_context_connect(pulse->context, NULL, 0, NULL))
+ {
+ WLog_Print(pulse->log, WLOG_ERROR, "pa_context_connect failed (%d)",
+ pa_context_errno(pulse->context));
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ pa_threaded_mainloop_lock(pulse->mainloop);
+
+ if (pa_threaded_mainloop_start(pulse->mainloop) < 0)
+ {
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ WLog_Print(pulse->log, WLOG_ERROR, "pa_threaded_mainloop_start failed (%d)",
+ pa_context_errno(pulse->context));
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ for (;;)
+ {
+ state = pa_context_get_state(pulse->context);
+
+ if (state == PA_CONTEXT_READY)
+ break;
+
+ if (!PA_CONTEXT_IS_GOOD(state))
+ {
+ WLog_Print(pulse->log, WLOG_ERROR, "bad context state (%s: %d)",
+ pulse_context_state_string(state), pa_context_errno(pulse->context));
+ pa_context_disconnect(pulse->context);
+ return ERROR_INVALID_STATE;
+ }
+
+ pa_threaded_mainloop_wait(pulse->mainloop);
+ }
+
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ WLog_Print(pulse->log, WLOG_DEBUG, "connected");
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_pulse_free(IAudinDevice* device)
+{
+ AudinPulseDevice* pulse = (AudinPulseDevice*)device;
+
+ if (!pulse)
+ return ERROR_INVALID_PARAMETER;
+
+ if (pulse->mainloop)
+ {
+ pa_threaded_mainloop_stop(pulse->mainloop);
+ }
+
+ if (pulse->context)
+ {
+ pa_context_disconnect(pulse->context);
+ pa_context_unref(pulse->context);
+ pulse->context = NULL;
+ }
+
+ if (pulse->mainloop)
+ {
+ pa_threaded_mainloop_free(pulse->mainloop);
+ pulse->mainloop = NULL;
+ }
+
+ free(pulse);
+ return CHANNEL_RC_OK;
+}
+
+static BOOL audin_pulse_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
+{
+ AudinPulseDevice* pulse = (AudinPulseDevice*)device;
+
+ if (!pulse || !format)
+ return FALSE;
+
+ if (!pulse->context)
+ return 0;
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ if (format->cbSize == 0 && (format->nSamplesPerSec <= PA_RATE_MAX) &&
+ (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
+ (format->nChannels >= 1 && format->nChannels <= PA_CHANNELS_MAX))
+ {
+ return TRUE;
+ }
+
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_pulse_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
+ UINT32 FramesPerPacket)
+{
+ pa_sample_spec sample_spec = { 0 };
+ AudinPulseDevice* pulse = (AudinPulseDevice*)device;
+
+ if (!pulse || !format)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!pulse->context)
+ return ERROR_INVALID_PARAMETER;
+
+ if (FramesPerPacket > 0)
+ pulse->frames_per_packet = FramesPerPacket;
+
+ sample_spec.rate = format->nSamplesPerSec;
+ sample_spec.channels = format->nChannels;
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM: /* PCM */
+ switch (format->wBitsPerSample)
+ {
+ case 8:
+ sample_spec.format = PA_SAMPLE_U8;
+ break;
+
+ case 16:
+ sample_spec.format = PA_SAMPLE_S16LE;
+ break;
+
+ default:
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ break;
+
+ case WAVE_FORMAT_ALAW: /* A-LAW */
+ sample_spec.format = PA_SAMPLE_ALAW;
+ break;
+
+ case WAVE_FORMAT_MULAW: /* U-LAW */
+ sample_spec.format = PA_SAMPLE_ULAW;
+ break;
+
+ default:
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ pulse->sample_spec = sample_spec;
+ pulse->format = *format;
+ return CHANNEL_RC_OK;
+}
+
+static void audin_pulse_stream_state_callback(pa_stream* stream, void* userdata)
+{
+ AudinPulseDevice* pulse = (AudinPulseDevice*)userdata;
+ pa_stream_state_t state = pa_stream_get_state(stream);
+
+ WLog_Print(pulse->log, WLOG_DEBUG, "stream state %s", pulse_stream_state_string(state));
+ switch (state)
+ {
+ case PA_STREAM_READY:
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+ break;
+
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+ break;
+
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ default:
+ break;
+ }
+}
+
+static void audin_pulse_stream_request_callback(pa_stream* stream, size_t length, void* userdata)
+{
+ const void* data = NULL;
+ AudinPulseDevice* pulse = (AudinPulseDevice*)userdata;
+ UINT error = CHANNEL_RC_OK;
+ pa_stream_peek(stream, &data, &length);
+ error =
+ IFCALLRESULT(CHANNEL_RC_OK, pulse->receive, &pulse->format, data, length, pulse->user_data);
+ pa_stream_drop(stream);
+
+ if (error && pulse->rdpcontext)
+ setChannelError(pulse->rdpcontext, error, "audin_pulse_thread_func reported an error");
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_pulse_close(IAudinDevice* device)
+{
+ AudinPulseDevice* pulse = (AudinPulseDevice*)device;
+
+ if (!pulse)
+ return ERROR_INVALID_PARAMETER;
+
+ if (pulse->stream)
+ {
+ pa_threaded_mainloop_lock(pulse->mainloop);
+ pa_stream_disconnect(pulse->stream);
+ pa_stream_unref(pulse->stream);
+ pulse->stream = NULL;
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ }
+
+ pulse->receive = NULL;
+ pulse->user_data = NULL;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_pulse_open(IAudinDevice* device, AudinReceive receive, void* user_data)
+{
+ pa_stream_state_t state = PA_STREAM_FAILED;
+ pa_buffer_attr buffer_attr = { 0 };
+ AudinPulseDevice* pulse = (AudinPulseDevice*)device;
+
+ if (!pulse || !receive || !user_data)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!pulse->context)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!pulse->sample_spec.rate || pulse->stream)
+ return ERROR_INVALID_PARAMETER;
+
+ pulse->receive = receive;
+ pulse->user_data = user_data;
+ pa_threaded_mainloop_lock(pulse->mainloop);
+ pulse->stream = pa_stream_new(pulse->context, "freerdp_audin", &pulse->sample_spec, NULL);
+
+ if (!pulse->stream)
+ {
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ WLog_Print(pulse->log, WLOG_DEBUG, "pa_stream_new failed (%d)",
+ pa_context_errno(pulse->context));
+ return pa_context_errno(pulse->context);
+ }
+
+ pulse->bytes_per_frame = pa_frame_size(&pulse->sample_spec);
+ pa_stream_set_state_callback(pulse->stream, audin_pulse_stream_state_callback, pulse);
+ pa_stream_set_read_callback(pulse->stream, audin_pulse_stream_request_callback, pulse);
+ buffer_attr.maxlength = (UINT32)-1;
+ buffer_attr.tlength = (UINT32)-1;
+ buffer_attr.prebuf = (UINT32)-1;
+ buffer_attr.minreq = (UINT32)-1;
+ /* 500ms latency */
+ buffer_attr.fragsize = pulse->bytes_per_frame * pulse->frames_per_packet;
+
+ if (buffer_attr.fragsize % pulse->format.nBlockAlign)
+ buffer_attr.fragsize +=
+ pulse->format.nBlockAlign - buffer_attr.fragsize % pulse->format.nBlockAlign;
+
+ if (pa_stream_connect_record(pulse->stream, pulse->device_name, &buffer_attr,
+ PA_STREAM_ADJUST_LATENCY) < 0)
+ {
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ WLog_Print(pulse->log, WLOG_ERROR, "pa_stream_connect_playback failed (%d)",
+ pa_context_errno(pulse->context));
+ return pa_context_errno(pulse->context);
+ }
+
+ for (;;)
+ {
+ state = pa_stream_get_state(pulse->stream);
+
+ if (state == PA_STREAM_READY)
+ break;
+
+ if (!PA_STREAM_IS_GOOD(state))
+ {
+ audin_pulse_close(device);
+ WLog_Print(pulse->log, WLOG_ERROR, "bad stream state (%s: %d)",
+ pulse_stream_state_string(state), pa_context_errno(pulse->context));
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ return pa_context_errno(pulse->context);
+ }
+
+ pa_threaded_mainloop_wait(pulse->mainloop);
+ }
+
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ pulse->buffer_frames = 0;
+ WLog_Print(pulse->log, WLOG_DEBUG, "connected");
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_pulse_parse_addin_args(AudinPulseDevice* device, const ADDIN_ARGV* args)
+{
+ int status = 0;
+ DWORD flags = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ AudinPulseDevice* pulse = (AudinPulseDevice*)device;
+ COMMAND_LINE_ARGUMENT_A audin_pulse_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
+ NULL, NULL, -1, NULL, "audio device name" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
+
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status = CommandLineParseArgumentsA(args->argc, args->argv, audin_pulse_args, flags, pulse,
+ NULL, NULL);
+
+ if (status < 0)
+ return ERROR_INVALID_PARAMETER;
+
+ arg = audin_pulse_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
+ {
+ pulse->device_name = _strdup(arg->Value);
+
+ if (!pulse->device_name)
+ {
+ WLog_Print(pulse->log, WLOG_ERROR, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT pulse_freerdp_audin_client_subsystem_entry(
+ PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ const ADDIN_ARGV* args = NULL;
+ AudinPulseDevice* pulse = NULL;
+ UINT error = 0;
+ pulse = (AudinPulseDevice*)calloc(1, sizeof(AudinPulseDevice));
+
+ if (!pulse)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ pulse->log = WLog_Get(TAG);
+ pulse->iface.Open = audin_pulse_open;
+ pulse->iface.FormatSupported = audin_pulse_format_supported;
+ pulse->iface.SetFormat = audin_pulse_set_format;
+ pulse->iface.Close = audin_pulse_close;
+ pulse->iface.Free = audin_pulse_free;
+ pulse->rdpcontext = pEntryPoints->rdpcontext;
+ args = pEntryPoints->args;
+
+ if ((error = audin_pulse_parse_addin_args(pulse, args)))
+ {
+ WLog_Print(pulse->log, WLOG_ERROR,
+ "audin_pulse_parse_addin_args failed with error %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ pulse->mainloop = pa_threaded_mainloop_new();
+
+ if (!pulse->mainloop)
+ {
+ WLog_Print(pulse->log, WLOG_ERROR, "pa_threaded_mainloop_new failed");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+
+ pulse->context = pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), "freerdp");
+
+ if (!pulse->context)
+ {
+ WLog_Print(pulse->log, WLOG_ERROR, "pa_context_new failed");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+
+ pa_context_set_state_callback(pulse->context, audin_pulse_context_state_callback, pulse);
+
+ if ((error = audin_pulse_connect(&pulse->iface)))
+ {
+ WLog_Print(pulse->log, WLOG_ERROR, "audin_pulse_connect failed");
+ goto error_out;
+ }
+
+ if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, &pulse->iface)))
+ {
+ WLog_Print(pulse->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!",
+ error);
+ goto error_out;
+ }
+
+ return CHANNEL_RC_OK;
+error_out:
+ audin_pulse_free(&pulse->iface);
+ return error;
+}
diff --git a/channels/audin/client/sndio/CMakeLists.txt b/channels/audin/client/sndio/CMakeLists.txt
new file mode 100644
index 0000000..ef68228
--- /dev/null
+++ b/channels/audin/client/sndio/CMakeLists.txt
@@ -0,0 +1,36 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
+# Copyright (c) 2020 Ingo Feinerer <feinerer@logic.at>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("audin" "sndio" "")
+
+find_package(SNDIO REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ audin_sndio.c)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${SNDIO_LIBRARIES}
+)
+
+include_directories(..)
+include_directories(${SNDIO_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
+
diff --git a/channels/audin/client/sndio/audin_sndio.c b/channels/audin/client/sndio/audin_sndio.c
new file mode 100644
index 0000000..92dac13
--- /dev/null
+++ b/channels/audin/client/sndio/audin_sndio.c
@@ -0,0 +1,352 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel - sndio implementation
+ *
+ * Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2020 Ingo Feinerer <feinerer@logic.at>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <sndio.h>
+
+#include <winpr/cmdline.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/rdpsnd.h>
+
+#include "audin_main.h"
+
+typedef struct
+{
+ IAudinDevice device;
+
+ HANDLE thread;
+ HANDLE stopEvent;
+
+ AUDIO_FORMAT format;
+ UINT32 FramesPerPacket;
+
+ AudinReceive receive;
+ void* user_data;
+
+ rdpContext* rdpcontext;
+} AudinSndioDevice;
+
+static BOOL audin_sndio_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
+{
+ if (device == NULL || format == NULL)
+ return FALSE;
+
+ return (format->wFormatTag == WAVE_FORMAT_PCM);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_sndio_set_format(IAudinDevice* device, AUDIO_FORMAT* format,
+ UINT32 FramesPerPacket)
+{
+ AudinSndioDevice* sndio = (AudinSndioDevice*)device;
+
+ if (device == NULL || format == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if (format->wFormatTag != WAVE_FORMAT_PCM)
+ return ERROR_INTERNAL_ERROR;
+
+ sndio->format = *format;
+ sndio->FramesPerPacket = FramesPerPacket;
+
+ return CHANNEL_RC_OK;
+}
+
+static void* audin_sndio_thread_func(void* arg)
+{
+ struct sio_hdl* hdl;
+ struct sio_par par;
+ BYTE* buffer = NULL;
+ size_t n, nbytes;
+ AudinSndioDevice* sndio = (AudinSndioDevice*)arg;
+ UINT error = 0;
+ DWORD status;
+
+ if (arg == NULL)
+ {
+ error = ERROR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ hdl = sio_open(SIO_DEVANY, SIO_REC, 0);
+ if (hdl == NULL)
+ {
+ WLog_ERR(TAG, "could not open audio device");
+ error = ERROR_INTERNAL_ERROR;
+ goto err_out;
+ }
+
+ sio_initpar(&par);
+ par.bits = sndio->format.wBitsPerSample;
+ par.rchan = sndio->format.nChannels;
+ par.rate = sndio->format.nSamplesPerSec;
+ if (!sio_setpar(hdl, &par))
+ {
+ WLog_ERR(TAG, "could not set audio parameters");
+ error = ERROR_INTERNAL_ERROR;
+ goto err_out;
+ }
+ if (!sio_getpar(hdl, &par))
+ {
+ WLog_ERR(TAG, "could not get audio parameters");
+ error = ERROR_INTERNAL_ERROR;
+ goto err_out;
+ }
+
+ if (!sio_start(hdl))
+ {
+ WLog_ERR(TAG, "could not start audio device");
+ error = ERROR_INTERNAL_ERROR;
+ goto err_out;
+ }
+
+ nbytes =
+ (sndio->FramesPerPacket * sndio->format.nChannels * (sndio->format.wBitsPerSample / 8));
+ buffer = (BYTE*)calloc((nbytes + sizeof(void*)), sizeof(BYTE));
+
+ if (buffer == NULL)
+ {
+ error = ERROR_NOT_ENOUGH_MEMORY;
+ goto err_out;
+ }
+
+ while (1)
+ {
+ status = WaitForSingleObject(sndio->stopEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ goto err_out;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ n = sio_read(hdl, buffer, nbytes);
+
+ if (n == 0)
+ {
+ WLog_ERR(TAG, "could not read");
+ continue;
+ }
+
+ if (n < nbytes)
+ continue;
+
+ if ((error = sndio->receive(&sndio->format, buffer, nbytes, sndio->user_data)))
+ {
+ WLog_ERR(TAG, "sndio->receive failed with error %" PRIu32 "", error);
+ break;
+ }
+ }
+
+err_out:
+ if (error && sndio->rdpcontext)
+ setChannelError(sndio->rdpcontext, error, "audin_sndio_thread_func reported an error");
+
+ if (hdl != NULL)
+ {
+ WLog_INFO(TAG, "sio_close");
+ sio_stop(hdl);
+ sio_close(hdl);
+ }
+
+ free(buffer);
+ ExitThread(0);
+ return NULL;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_sndio_open(IAudinDevice* device, AudinReceive receive, void* user_data)
+{
+ AudinSndioDevice* sndio = (AudinSndioDevice*)device;
+ sndio->receive = receive;
+ sndio->user_data = user_data;
+
+ if (!(sndio->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_ERR(TAG, "CreateEvent failed");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!(sndio->thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)audin_sndio_thread_func,
+ sndio, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateThread failed");
+ CloseHandle(sndio->stopEvent);
+ sndio->stopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_sndio_close(IAudinDevice* device)
+{
+ UINT error;
+ AudinSndioDevice* sndio = (AudinSndioDevice*)device;
+
+ if (device == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if (sndio->stopEvent != NULL)
+ {
+ SetEvent(sndio->stopEvent);
+
+ if (WaitForSingleObject(sndio->thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ CloseHandle(sndio->stopEvent);
+ sndio->stopEvent = NULL;
+ CloseHandle(sndio->thread);
+ sndio->thread = NULL;
+ }
+
+ sndio->receive = NULL;
+ sndio->user_data = NULL;
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_sndio_free(IAudinDevice* device)
+{
+ AudinSndioDevice* sndio = (AudinSndioDevice*)device;
+ int error;
+
+ if (device == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = audin_sndio_close(device)))
+ {
+ WLog_ERR(TAG, "audin_sndio_close failed with error code %d", error);
+ }
+
+ free(sndio);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_sndio_parse_addin_args(AudinSndioDevice* device, ADDIN_ARGV* args)
+{
+ int status;
+ DWORD flags;
+ COMMAND_LINE_ARGUMENT_A* arg;
+ AudinSndioDevice* sndio = (AudinSndioDevice*)device;
+ COMMAND_LINE_ARGUMENT_A audin_sndio_args[] = { { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status = CommandLineParseArgumentsA(args->argc, (const char**)args->argv, audin_sndio_args,
+ flags, sndio, NULL, NULL);
+
+ if (status < 0)
+ return ERROR_INVALID_PARAMETER;
+
+ arg = audin_sndio_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT sndio_freerdp_audin_client_subsystem_entry(
+ PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ ADDIN_ARGV* args;
+ AudinSndioDevice* sndio;
+ UINT ret = CHANNEL_RC_OK;
+ sndio = (AudinSndioDevice*)calloc(1, sizeof(AudinSndioDevice));
+
+ if (sndio == NULL)
+ return CHANNEL_RC_NO_MEMORY;
+
+ sndio->device.Open = audin_sndio_open;
+ sndio->device.FormatSupported = audin_sndio_format_supported;
+ sndio->device.SetFormat = audin_sndio_set_format;
+ sndio->device.Close = audin_sndio_close;
+ sndio->device.Free = audin_sndio_free;
+ sndio->rdpcontext = pEntryPoints->rdpcontext;
+ args = pEntryPoints->args;
+
+ if (args->argc > 1)
+ {
+ ret = audin_sndio_parse_addin_args(sndio, args);
+
+ if (ret != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "error parsing arguments");
+ goto error;
+ }
+ }
+
+ if ((ret = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)sndio)))
+ {
+ WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "", ret);
+ goto error;
+ }
+
+ return ret;
+error:
+ audin_sndio_free(&sndio->device);
+ return ret;
+}
diff --git a/channels/audin/client/winmm/CMakeLists.txt b/channels/audin/client/winmm/CMakeLists.txt
new file mode 100644
index 0000000..8657942
--- /dev/null
+++ b/channels/audin/client/winmm/CMakeLists.txt
@@ -0,0 +1,31 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("audin" "winmm" "")
+
+set(${MODULE_PREFIX}_SRCS
+ audin_winmm.c)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ winmm.lib
+)
+
+include_directories(..)
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/audin/client/winmm/audin_winmm.c b/channels/audin/client/winmm/audin_winmm.c
new file mode 100644
index 0000000..b4172a5
--- /dev/null
+++ b/channels/audin/client/winmm/audin_winmm.c
@@ -0,0 +1,566 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel - WinMM implementation
+ *
+ * Copyright 2013 Zhang Zhaolong <zhangzl2013@126.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <windows.h>
+#include <mmsystem.h>
+
+#include <winpr/crt.h>
+#include <winpr/cmdline.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/addin.h>
+#include <freerdp/client/audin.h>
+
+#include "audin_main.h"
+
+/* fix missing definitions in mingw */
+#ifndef WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE
+#define WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE 0x0010
+#endif
+
+typedef struct
+{
+ IAudinDevice iface;
+
+ char* device_name;
+ AudinReceive receive;
+ void* user_data;
+ HANDLE thread;
+ HANDLE stopEvent;
+ HWAVEIN hWaveIn;
+ PWAVEFORMATEX* ppwfx;
+ PWAVEFORMATEX pwfx_cur;
+ UINT32 ppwfx_size;
+ UINT32 cFormats;
+ UINT32 frames_per_packet;
+ rdpContext* rdpcontext;
+ wLog* log;
+} AudinWinmmDevice;
+
+static void CALLBACK waveInProc(HWAVEIN hWaveIn, UINT uMsg, DWORD_PTR dwInstance,
+ DWORD_PTR dwParam1, DWORD_PTR dwParam2)
+{
+ AudinWinmmDevice* winmm = (AudinWinmmDevice*)dwInstance;
+ PWAVEHDR pWaveHdr;
+ UINT error = CHANNEL_RC_OK;
+ MMRESULT mmResult;
+
+ switch (uMsg)
+ {
+ case WIM_CLOSE:
+ break;
+
+ case WIM_DATA:
+ pWaveHdr = (WAVEHDR*)dwParam1;
+
+ if (WHDR_DONE == (WHDR_DONE & pWaveHdr->dwFlags))
+ {
+ if (pWaveHdr->dwBytesRecorded &&
+ !(WaitForSingleObject(winmm->stopEvent, 0) == WAIT_OBJECT_0))
+ {
+ AUDIO_FORMAT format;
+ format.cbSize = winmm->pwfx_cur->cbSize;
+ format.nBlockAlign = winmm->pwfx_cur->nBlockAlign;
+ format.nAvgBytesPerSec = winmm->pwfx_cur->nAvgBytesPerSec;
+ format.nChannels = winmm->pwfx_cur->nChannels;
+ format.nSamplesPerSec = winmm->pwfx_cur->nSamplesPerSec;
+ format.wBitsPerSample = winmm->pwfx_cur->wBitsPerSample;
+ format.wFormatTag = winmm->pwfx_cur->wFormatTag;
+
+ if ((error = winmm->receive(&format, pWaveHdr->lpData,
+ pWaveHdr->dwBytesRecorded, winmm->user_data)))
+ break;
+
+ mmResult = waveInAddBuffer(hWaveIn, pWaveHdr, sizeof(WAVEHDR));
+
+ if (mmResult != MMSYSERR_NOERROR)
+ error = ERROR_INTERNAL_ERROR;
+ }
+ }
+
+ break;
+
+ case WIM_OPEN:
+ break;
+
+ default:
+ break;
+ }
+
+ if (error && winmm->rdpcontext)
+ setChannelError(winmm->rdpcontext, error, "waveInProc reported an error");
+}
+
+static BOOL log_mmresult(AudinWinmmDevice* winmm, const char* what, MMRESULT result)
+{
+ if (result != MMSYSERR_NOERROR)
+ {
+ CHAR buffer[8192] = { 0 };
+ CHAR msg[8192] = { 0 };
+ CHAR cmsg[8192] = { 0 };
+ waveInGetErrorTextA(result, buffer, sizeof(buffer));
+
+ _snprintf(msg, sizeof(msg) - 1, "%s failed. %" PRIu32 " [%s]", what, result, buffer);
+ _snprintf(cmsg, sizeof(cmsg) - 1, "audin_winmm_thread_func reported an error '%s'", msg);
+ WLog_Print(winmm->log, WLOG_DEBUG, "%s", msg);
+ if (winmm->rdpcontext)
+ setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR, cmsg);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL test_format_supported(const PWAVEFORMATEX pwfx)
+{
+ MMRESULT rc;
+ WAVEINCAPSA caps = { 0 };
+
+ rc = waveInGetDevCapsA(WAVE_MAPPER, &caps, sizeof(caps));
+ if (rc != MMSYSERR_NOERROR)
+ return FALSE;
+
+ switch (pwfx->nChannels)
+ {
+ case 1:
+ if ((caps.dwFormats &
+ (WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_96M08 |
+ WAVE_FORMAT_1M16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_4M16 | WAVE_FORMAT_96M16)) == 0)
+ return FALSE;
+ break;
+ case 2:
+ if ((caps.dwFormats &
+ (WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_96S08 |
+ WAVE_FORMAT_1S16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4S16 | WAVE_FORMAT_96S16)) == 0)
+ return FALSE;
+ break;
+ default:
+ return FALSE;
+ }
+
+ rc = waveInOpen(NULL, WAVE_MAPPER, pwfx, 0, 0,
+ WAVE_FORMAT_QUERY | WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE);
+ return (rc == MMSYSERR_NOERROR);
+}
+
+static DWORD WINAPI audin_winmm_thread_func(LPVOID arg)
+{
+ AudinWinmmDevice* winmm = (AudinWinmmDevice*)arg;
+ char* buffer = NULL;
+ int size = 0;
+ WAVEHDR waveHdr[4] = { 0 };
+ DWORD status = 0;
+ MMRESULT rc = 0;
+
+ if (!winmm->hWaveIn)
+ {
+ MMRESULT rc;
+ rc = waveInOpen(&winmm->hWaveIn, WAVE_MAPPER, winmm->pwfx_cur, (DWORD_PTR)waveInProc,
+ (DWORD_PTR)winmm,
+ CALLBACK_FUNCTION | WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE);
+ if (!log_mmresult(winmm, "waveInOpen", rc))
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ size =
+ (winmm->pwfx_cur->wBitsPerSample * winmm->pwfx_cur->nChannels * winmm->frames_per_packet +
+ 7) /
+ 8;
+
+ for (int i = 0; i < 4; i++)
+ {
+ buffer = (char*)malloc(size);
+
+ if (!buffer)
+ return CHANNEL_RC_NO_MEMORY;
+
+ waveHdr[i].dwBufferLength = size;
+ waveHdr[i].dwFlags = 0;
+ waveHdr[i].lpData = buffer;
+ rc = waveInPrepareHeader(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i]));
+
+ if (!log_mmresult(winmm, "waveInPrepareHeader", rc))
+ {
+ }
+
+ rc = waveInAddBuffer(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i]));
+
+ if (!log_mmresult(winmm, "waveInAddBuffer", rc))
+ {
+ }
+ }
+
+ rc = waveInStart(winmm->hWaveIn);
+
+ if (!log_mmresult(winmm, "waveInStart", rc))
+ {
+ }
+
+ status = WaitForSingleObject(winmm->stopEvent, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ WLog_Print(winmm->log, WLOG_DEBUG, "WaitForSingleObject failed.");
+
+ if (winmm->rdpcontext)
+ setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR,
+ "audin_winmm_thread_func reported an error");
+ }
+
+ rc = waveInReset(winmm->hWaveIn);
+
+ if (!log_mmresult(winmm, "waveInReset", rc))
+ {
+ }
+
+ for (int i = 0; i < 4; i++)
+ {
+ rc = waveInUnprepareHeader(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i]));
+
+ if (!log_mmresult(winmm, "waveInUnprepareHeader", rc))
+ {
+ }
+
+ free(waveHdr[i].lpData);
+ }
+
+ rc = waveInClose(winmm->hWaveIn);
+
+ if (!log_mmresult(winmm, "waveInClose", rc))
+ {
+ }
+
+ winmm->hWaveIn = NULL;
+ return 0;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_winmm_free(IAudinDevice* device)
+{
+ AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
+
+ if (!winmm)
+ return ERROR_INVALID_PARAMETER;
+
+ for (UINT32 i = 0; i < winmm->cFormats; i++)
+ {
+ free(winmm->ppwfx[i]);
+ }
+
+ free(winmm->ppwfx);
+ free(winmm->device_name);
+ free(winmm);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_winmm_close(IAudinDevice* device)
+{
+ DWORD status;
+ UINT error = CHANNEL_RC_OK;
+ AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
+
+ if (!winmm)
+ return ERROR_INVALID_PARAMETER;
+
+ SetEvent(winmm->stopEvent);
+ status = WaitForSingleObject(winmm->thread, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_Print(winmm->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ CloseHandle(winmm->thread);
+ CloseHandle(winmm->stopEvent);
+ winmm->thread = NULL;
+ winmm->stopEvent = NULL;
+ winmm->receive = NULL;
+ winmm->user_data = NULL;
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_winmm_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
+ UINT32 FramesPerPacket)
+{
+ AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
+
+ if (!winmm || !format)
+ return ERROR_INVALID_PARAMETER;
+
+ winmm->frames_per_packet = FramesPerPacket;
+
+ for (UINT32 i = 0; i < winmm->cFormats; i++)
+ {
+ const PWAVEFORMATEX ppwfx = winmm->ppwfx[i];
+ if ((ppwfx->wFormatTag == format->wFormatTag) && (ppwfx->nChannels == format->nChannels) &&
+ (ppwfx->wBitsPerSample == format->wBitsPerSample) &&
+ (ppwfx->nSamplesPerSec == format->nSamplesPerSec))
+ {
+ /* BUG: Many devices report to support stereo recording but fail here.
+ * Ensure we always use mono. */
+ if (ppwfx->nChannels > 1)
+ {
+ ppwfx->nChannels = 1;
+ }
+
+ if (ppwfx->nBlockAlign != 2)
+ {
+ ppwfx->nBlockAlign = 2;
+ ppwfx->nAvgBytesPerSec = ppwfx->nSamplesPerSec * ppwfx->nBlockAlign;
+ }
+
+ if (!test_format_supported(ppwfx))
+ return ERROR_INVALID_PARAMETER;
+ winmm->pwfx_cur = ppwfx;
+ return CHANNEL_RC_OK;
+ }
+ }
+
+ return ERROR_INVALID_PARAMETER;
+}
+
+static BOOL audin_winmm_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
+{
+ AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
+ PWAVEFORMATEX pwfx;
+ BYTE* data;
+
+ if (!winmm || !format)
+ return FALSE;
+
+ if (format->wFormatTag != WAVE_FORMAT_PCM)
+ return FALSE;
+
+ if (format->nChannels != 1)
+ return FALSE;
+
+ pwfx = (PWAVEFORMATEX)malloc(sizeof(WAVEFORMATEX) + format->cbSize);
+
+ if (!pwfx)
+ return FALSE;
+
+ pwfx->cbSize = format->cbSize;
+ pwfx->wFormatTag = format->wFormatTag;
+ pwfx->nChannels = format->nChannels;
+ pwfx->nSamplesPerSec = format->nSamplesPerSec;
+ pwfx->nBlockAlign = format->nBlockAlign;
+ pwfx->wBitsPerSample = format->wBitsPerSample;
+ data = (BYTE*)pwfx + sizeof(WAVEFORMATEX);
+ memcpy(data, format->data, format->cbSize);
+
+ pwfx->nAvgBytesPerSec = pwfx->nSamplesPerSec * pwfx->nBlockAlign;
+
+ if (!test_format_supported(pwfx))
+ goto fail;
+
+ if (winmm->cFormats >= winmm->ppwfx_size)
+ {
+ PWAVEFORMATEX* tmp_ppwfx;
+ tmp_ppwfx = realloc(winmm->ppwfx, sizeof(PWAVEFORMATEX) * winmm->ppwfx_size * 2);
+
+ if (!tmp_ppwfx)
+ goto fail;
+
+ winmm->ppwfx_size *= 2;
+ winmm->ppwfx = tmp_ppwfx;
+ }
+
+ winmm->ppwfx[winmm->cFormats++] = pwfx;
+ return TRUE;
+
+fail:
+ free(pwfx);
+ return FALSE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_winmm_open(IAudinDevice* device, AudinReceive receive, void* user_data)
+{
+ AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
+
+ if (!winmm || !receive || !user_data)
+ return ERROR_INVALID_PARAMETER;
+
+ winmm->receive = receive;
+ winmm->user_data = user_data;
+
+ if (!(winmm->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!(winmm->thread = CreateThread(NULL, 0, audin_winmm_thread_func, winmm, 0, NULL)))
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "CreateThread failed!");
+ CloseHandle(winmm->stopEvent);
+ winmm->stopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT audin_winmm_parse_addin_args(AudinWinmmDevice* device, const ADDIN_ARGV* args)
+{
+ int status;
+ DWORD flags;
+ const COMMAND_LINE_ARGUMENT_A* arg;
+ AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
+ COMMAND_LINE_ARGUMENT_A audin_winmm_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
+ NULL, NULL, -1, NULL, "audio device name" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
+
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status = CommandLineParseArgumentsA(args->argc, args->argv, audin_winmm_args, flags, winmm,
+ NULL, NULL);
+ arg = audin_winmm_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
+ {
+ winmm->device_name = _strdup(arg->Value);
+
+ if (!winmm->device_name)
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT winmm_freerdp_audin_client_subsystem_entry(
+ PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ const ADDIN_ARGV* args;
+ AudinWinmmDevice* winmm;
+ UINT error;
+
+ if (waveInGetNumDevs() == 0)
+ {
+ WLog_Print(WLog_Get(TAG), WLOG_ERROR, "No microphone available!");
+ return ERROR_DEVICE_NOT_AVAILABLE;
+ }
+
+ winmm = (AudinWinmmDevice*)calloc(1, sizeof(AudinWinmmDevice));
+
+ if (!winmm)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ winmm->log = WLog_Get(TAG);
+ winmm->iface.Open = audin_winmm_open;
+ winmm->iface.FormatSupported = audin_winmm_format_supported;
+ winmm->iface.SetFormat = audin_winmm_set_format;
+ winmm->iface.Close = audin_winmm_close;
+ winmm->iface.Free = audin_winmm_free;
+ winmm->rdpcontext = pEntryPoints->rdpcontext;
+ args = pEntryPoints->args;
+
+ if ((error = audin_winmm_parse_addin_args(winmm, args)))
+ {
+ WLog_Print(winmm->log, WLOG_ERROR,
+ "audin_winmm_parse_addin_args failed with error %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ if (!winmm->device_name)
+ {
+ winmm->device_name = _strdup("default");
+
+ if (!winmm->device_name)
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "_strdup failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+ }
+
+ winmm->ppwfx_size = 10;
+ winmm->ppwfx = calloc(winmm->ppwfx_size, sizeof(PWAVEFORMATEX));
+
+ if (!winmm->ppwfx)
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "malloc failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+
+ if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, &winmm->iface)))
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!",
+ error);
+ goto error_out;
+ }
+
+ return CHANNEL_RC_OK;
+error_out:
+ free(winmm->ppwfx);
+ free(winmm->device_name);
+ free(winmm);
+ return error;
+}
diff --git a/channels/audin/server/CMakeLists.txt b/channels/audin/server/CMakeLists.txt
new file mode 100644
index 0000000..454d2a6
--- /dev/null
+++ b/channels/audin/server/CMakeLists.txt
@@ -0,0 +1,27 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_server("audin")
+
+set(${MODULE_PREFIX}_SRCS
+ audin.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry")
diff --git a/channels/audin/server/audin.c b/channels/audin/server/audin.c
new file mode 100644
index 0000000..d67937a
--- /dev/null
+++ b/channels/audin/server/audin.c
@@ -0,0 +1,914 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Server Audio Input Virtual Channel
+ *
+ * Copyright 2012 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2022 Pascal Nowack <Pascal.Nowack@gmx.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/server/server-common.h>
+#include <freerdp/server/audin.h>
+#include <freerdp/channels/log.h>
+
+#define AUDIN_TAG CHANNELS_TAG("audin.server")
+
+#define SNDIN_HEADER_SIZE 1
+
+typedef enum
+{
+ MSG_SNDIN_VERSION = 0x01,
+ MSG_SNDIN_FORMATS = 0x02,
+ MSG_SNDIN_OPEN = 0x03,
+ MSG_SNDIN_OPEN_REPLY = 0x04,
+ MSG_SNDIN_DATA_INCOMING = 0x05,
+ MSG_SNDIN_DATA = 0x06,
+ MSG_SNDIN_FORMATCHANGE = 0x07,
+} MSG_SNDIN;
+
+typedef struct
+{
+ audin_server_context context;
+
+ HANDLE stopEvent;
+
+ HANDLE thread;
+ void* audin_channel;
+
+ DWORD SessionId;
+
+ AUDIO_FORMAT* audin_server_formats;
+ UINT32 audin_n_server_formats;
+ AUDIO_FORMAT* audin_negotiated_format;
+ UINT32 audin_client_format_idx;
+ wLog* log;
+} audin_server;
+
+static UINT audin_server_recv_version(audin_server_context* context, wStream* s,
+ const SNDIN_PDU* header)
+{
+ audin_server* audin = (audin_server*)context;
+ SNDIN_VERSION pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT32(s, pdu.Version);
+
+ IFCALLRET(context->ReceiveVersion, error, context, &pdu);
+ if (error)
+ WLog_Print(audin->log, WLOG_ERROR, "context->ReceiveVersion failed with error %" PRIu32 "",
+ error);
+
+ return error;
+}
+
+static UINT audin_server_recv_formats(audin_server_context* context, wStream* s,
+ const SNDIN_PDU* header)
+{
+ audin_server* audin = (audin_server*)context;
+ SNDIN_FORMATS pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ /* Implementations MUST, at a minimum, support WAVE_FORMAT_PCM (0x0001) */
+ if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4 + 4 + 18))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT32(s, pdu.NumFormats);
+ Stream_Read_UINT32(s, pdu.cbSizeFormatsPacket);
+
+ if (pdu.NumFormats == 0)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Sound Formats PDU contains no formats");
+ return ERROR_INVALID_DATA;
+ }
+
+ pdu.SoundFormats = audio_formats_new(pdu.NumFormats);
+ if (!pdu.SoundFormats)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Failed to allocate %u SoundFormats", pdu.NumFormats);
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ for (UINT32 i = 0; i < pdu.NumFormats; ++i)
+ {
+ AUDIO_FORMAT* format = &pdu.SoundFormats[i];
+
+ if (!audio_format_read(s, format))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Failed to read audio format");
+ audio_formats_free(pdu.SoundFormats, i + i);
+ return ERROR_INVALID_DATA;
+ }
+
+ audio_format_print(audin->log, WLOG_DEBUG, format);
+ }
+
+ if (pdu.cbSizeFormatsPacket != Stream_GetPosition(s))
+ {
+ WLog_Print(audin->log, WLOG_WARN,
+ "cbSizeFormatsPacket is invalid! Expected: %u Got: %zu. Fixing size",
+ pdu.cbSizeFormatsPacket, Stream_GetPosition(s));
+ const size_t pos = Stream_GetPosition(s);
+ if (pos > UINT32_MAX)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Stream too long, %" PRIuz " exceeds UINT32_MAX",
+ pos);
+ error = ERROR_INVALID_PARAMETER;
+ goto fail;
+ }
+ pdu.cbSizeFormatsPacket = (UINT32)pos;
+ }
+
+ pdu.ExtraDataSize = Stream_GetRemainingLength(s);
+
+ IFCALLRET(context->ReceiveFormats, error, context, &pdu);
+ if (error)
+ WLog_Print(audin->log, WLOG_ERROR, "context->ReceiveFormats failed with error %" PRIu32 "",
+ error);
+
+fail:
+ audio_formats_free(pdu.SoundFormats, pdu.NumFormats);
+
+ return error;
+}
+
+static UINT audin_server_recv_open_reply(audin_server_context* context, wStream* s,
+ const SNDIN_PDU* header)
+{
+ audin_server* audin = (audin_server*)context;
+ SNDIN_OPEN_REPLY pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT32(s, pdu.Result);
+
+ IFCALLRET(context->OpenReply, error, context, &pdu);
+ if (error)
+ WLog_Print(audin->log, WLOG_ERROR, "context->OpenReply failed with error %" PRIu32 "",
+ error);
+
+ return error;
+}
+
+static UINT audin_server_recv_data_incoming(audin_server_context* context, wStream* s,
+ const SNDIN_PDU* header)
+{
+ audin_server* audin = (audin_server*)context;
+ SNDIN_DATA_INCOMING pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ IFCALLRET(context->IncomingData, error, context, &pdu);
+ if (error)
+ WLog_Print(audin->log, WLOG_ERROR, "context->IncomingData failed with error %" PRIu32 "",
+ error);
+
+ return error;
+}
+
+static UINT audin_server_recv_data(audin_server_context* context, wStream* s,
+ const SNDIN_PDU* header)
+{
+ audin_server* audin = (audin_server*)context;
+ SNDIN_DATA pdu = { 0 };
+ wStream dataBuffer = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ pdu.Data = Stream_StaticInit(&dataBuffer, Stream_Pointer(s), Stream_GetRemainingLength(s));
+
+ IFCALLRET(context->Data, error, context, &pdu);
+ if (error)
+ WLog_Print(audin->log, WLOG_ERROR, "context->Data failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT audin_server_recv_format_change(audin_server_context* context, wStream* s,
+ const SNDIN_PDU* header)
+{
+ audin_server* audin = (audin_server*)context;
+ SNDIN_FORMATCHANGE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT32(s, pdu.NewFormat);
+
+ IFCALLRET(context->ReceiveFormatChange, error, context, &pdu);
+ if (error)
+ WLog_Print(audin->log, WLOG_ERROR,
+ "context->ReceiveFormatChange failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static DWORD WINAPI audin_server_thread_func(LPVOID arg)
+{
+ wStream* s = NULL;
+ void* buffer = NULL;
+ DWORD nCount = 0;
+ HANDLE events[8] = { 0 };
+ BOOL ready = FALSE;
+ HANDLE ChannelEvent = NULL;
+ DWORD BytesReturned = 0;
+ audin_server* audin = (audin_server*)arg;
+ UINT error = CHANNEL_RC_OK;
+ DWORD status = ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(audin);
+
+ if (WTSVirtualChannelQuery(audin->audin_channel, WTSVirtualEventHandle, &buffer,
+ &BytesReturned) == TRUE)
+ {
+ if (BytesReturned == sizeof(HANDLE))
+ CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE));
+
+ WTSFreeMemory(buffer);
+ }
+ else
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelQuery failed");
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ nCount = 0;
+ events[nCount++] = audin->stopEvent;
+ events[nCount++] = ChannelEvent;
+
+ /* Wait for the client to confirm that the Audio Input dynamic channel is ready */
+
+ while (1)
+ {
+ if ((status = WaitForMultipleObjects(nCount, events, FALSE, 100)) == WAIT_OBJECT_0)
+ goto out;
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_Print(audin->log, WLOG_ERROR,
+ "WaitForMultipleObjects failed with error %" PRIu32 "", error);
+ goto out;
+ }
+ if (status == WAIT_OBJECT_0)
+ goto out;
+
+ if (WTSVirtualChannelQuery(audin->audin_channel, WTSVirtualChannelReady, &buffer,
+ &BytesReturned) == FALSE)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelQuery failed");
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ ready = *((BOOL*)buffer);
+ WTSFreeMemory(buffer);
+
+ if (ready)
+ break;
+ }
+
+ s = Stream_New(NULL, 4096);
+
+ if (!s)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ if (ready)
+ {
+ SNDIN_VERSION version = { 0 };
+
+ version.Version = audin->context.serverVersion;
+
+ if ((error = audin->context.SendVersion(&audin->context, &version)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "SendVersion failed with error %" PRIu32 "!", error);
+ goto out_capacity;
+ }
+ }
+
+ while (ready)
+ {
+ SNDIN_PDU header = { 0 };
+
+ if ((status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE)) == WAIT_OBJECT_0)
+ break;
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_Print(audin->log, WLOG_ERROR,
+ "WaitForMultipleObjects failed with error %" PRIu32 "", error);
+ break;
+ }
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ Stream_SetPosition(s, 0);
+
+ if (!WTSVirtualChannelRead(audin->audin_channel, 0, NULL, 0, &BytesReturned))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelRead failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (BytesReturned < 1)
+ continue;
+
+ if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
+ break;
+
+ WINPR_ASSERT(Stream_Capacity(s) <= ULONG_MAX);
+ if (WTSVirtualChannelRead(audin->audin_channel, 0, (PCHAR)Stream_Buffer(s),
+ (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelRead failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ Stream_SetLength(s, BytesReturned);
+ if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, SNDIN_HEADER_SIZE))
+ {
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ Stream_Read_UINT8(s, header.MessageId);
+
+ switch (header.MessageId)
+ {
+ case MSG_SNDIN_VERSION:
+ error = audin_server_recv_version(&audin->context, s, &header);
+ break;
+ case MSG_SNDIN_FORMATS:
+ error = audin_server_recv_formats(&audin->context, s, &header);
+ break;
+ case MSG_SNDIN_OPEN_REPLY:
+ error = audin_server_recv_open_reply(&audin->context, s, &header);
+ break;
+ case MSG_SNDIN_DATA_INCOMING:
+ error = audin_server_recv_data_incoming(&audin->context, s, &header);
+ break;
+ case MSG_SNDIN_DATA:
+ error = audin_server_recv_data(&audin->context, s, &header);
+ break;
+ case MSG_SNDIN_FORMATCHANGE:
+ error = audin_server_recv_format_change(&audin->context, s, &header);
+ break;
+ default:
+ WLog_Print(audin->log, WLOG_ERROR,
+ "audin_server_thread_func: unknown or invalid MessageId %" PRIu8 "",
+ header.MessageId);
+ error = ERROR_INVALID_DATA;
+ break;
+ }
+ if (error)
+ break;
+ }
+
+out_capacity:
+ Stream_Free(s, TRUE);
+out:
+ WTSVirtualChannelClose(audin->audin_channel);
+ audin->audin_channel = NULL;
+
+ if (error && audin->context.rdpcontext)
+ setChannelError(audin->context.rdpcontext, error,
+ "audin_server_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+static BOOL audin_server_open(audin_server_context* context)
+{
+ audin_server* audin = (audin_server*)context;
+
+ WINPR_ASSERT(audin);
+ if (!audin->thread)
+ {
+ PULONG pSessionId = NULL;
+ DWORD BytesReturned = 0;
+ audin->SessionId = WTS_CURRENT_SESSION;
+ UINT32 channelId = 0;
+ BOOL status = TRUE;
+
+ if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId,
+ (LPSTR*)&pSessionId, &BytesReturned))
+ {
+ audin->SessionId = (DWORD)*pSessionId;
+ WTSFreeMemory(pSessionId);
+ }
+
+ audin->audin_channel = WTSVirtualChannelOpenEx(audin->SessionId, AUDIN_DVC_CHANNEL_NAME,
+ WTS_CHANNEL_OPTION_DYNAMIC);
+
+ if (!audin->audin_channel)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelOpenEx failed!");
+ return FALSE;
+ }
+
+ channelId = WTSChannelGetIdByHandle(audin->audin_channel);
+
+ IFCALLRET(context->ChannelIdAssigned, status, context, channelId);
+ if (!status)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "context->ChannelIdAssigned failed!");
+ return FALSE;
+ }
+
+ if (!(audin->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "CreateEvent failed!");
+ return FALSE;
+ }
+
+ if (!(audin->thread =
+ CreateThread(NULL, 0, audin_server_thread_func, (void*)audin, 0, NULL)))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "CreateThread failed!");
+ CloseHandle(audin->stopEvent);
+ audin->stopEvent = NULL;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ WLog_Print(audin->log, WLOG_ERROR, "thread already running!");
+ return FALSE;
+}
+
+static BOOL audin_server_is_open(audin_server_context* context)
+{
+ audin_server* audin = (audin_server*)context;
+
+ WINPR_ASSERT(audin);
+ return audin->thread != NULL;
+}
+
+static BOOL audin_server_close(audin_server_context* context)
+{
+ audin_server* audin = (audin_server*)context;
+ WINPR_ASSERT(audin);
+
+ if (audin->thread)
+ {
+ SetEvent(audin->stopEvent);
+
+ if (WaitForSingleObject(audin->thread, INFINITE) == WAIT_FAILED)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "",
+ GetLastError());
+ return FALSE;
+ }
+
+ CloseHandle(audin->thread);
+ CloseHandle(audin->stopEvent);
+ audin->thread = NULL;
+ audin->stopEvent = NULL;
+ }
+
+ if (audin->audin_channel)
+ {
+ WTSVirtualChannelClose(audin->audin_channel);
+ audin->audin_channel = NULL;
+ }
+
+ audin->audin_negotiated_format = NULL;
+
+ return TRUE;
+}
+
+static wStream* audin_server_packet_new(wLog* log, size_t size, BYTE MessageId)
+{
+ WINPR_ASSERT(log);
+
+ /* Allocate what we need plus header bytes */
+ wStream* s = Stream_New(NULL, size + SNDIN_HEADER_SIZE);
+ if (!s)
+ {
+ WLog_Print(log, WLOG_ERROR, "Stream_New failed!");
+ return NULL;
+ }
+
+ Stream_Write_UINT8(s, MessageId);
+
+ return s;
+}
+
+static UINT audin_server_packet_send(audin_server_context* context, wStream* s)
+{
+ audin_server* audin = (audin_server*)context;
+ UINT error = CHANNEL_RC_OK;
+ ULONG written = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(s);
+
+ const size_t pos = Stream_GetPosition(s);
+ if (pos > UINT32_MAX)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!WTSVirtualChannelWrite(audin->audin_channel, (PCHAR)Stream_Buffer(s), (UINT32)pos,
+ &written))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelWrite failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ if (written < Stream_GetPosition(s))
+ {
+ WLog_Print(audin->log, WLOG_WARN, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "",
+ written, Stream_GetPosition(s));
+ }
+
+out:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+static UINT audin_server_send_version(audin_server_context* context, const SNDIN_VERSION* version)
+{
+ audin_server* audin = (audin_server*)context;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(version);
+
+ wStream* s = audin_server_packet_new(audin->log, 4, MSG_SNDIN_VERSION);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT32(s, version->Version);
+
+ return audin_server_packet_send(context, s);
+}
+
+static UINT audin_server_send_formats(audin_server_context* context, const SNDIN_FORMATS* formats)
+{
+ audin_server* audin = (audin_server*)context;
+
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(formats);
+
+ wStream* s = audin_server_packet_new(audin->log, 4 + 4 + 18, MSG_SNDIN_FORMATS);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT32(s, formats->NumFormats);
+ Stream_Write_UINT32(s, formats->cbSizeFormatsPacket);
+
+ for (UINT32 i = 0; i < formats->NumFormats; ++i)
+ {
+ AUDIO_FORMAT* format = &formats->SoundFormats[i];
+
+ if (!audio_format_write(s, format))
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Failed to write audio format");
+ Stream_Free(s, TRUE);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+
+ return audin_server_packet_send(context, s);
+}
+
+static UINT audin_server_send_open(audin_server_context* context, const SNDIN_OPEN* open)
+{
+ audin_server* audin = (audin_server*)context;
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(open);
+
+ wStream* s = audin_server_packet_new(audin->log, 4 + 4 + 18 + 22, MSG_SNDIN_OPEN);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT32(s, open->FramesPerPacket);
+ Stream_Write_UINT32(s, open->initialFormat);
+
+ Stream_Write_UINT16(s, open->captureFormat.wFormatTag);
+ Stream_Write_UINT16(s, open->captureFormat.nChannels);
+ Stream_Write_UINT32(s, open->captureFormat.nSamplesPerSec);
+ Stream_Write_UINT32(s, open->captureFormat.nAvgBytesPerSec);
+ Stream_Write_UINT16(s, open->captureFormat.nBlockAlign);
+ Stream_Write_UINT16(s, open->captureFormat.wBitsPerSample);
+
+ if (open->ExtraFormatData)
+ {
+ Stream_Write_UINT16(s, 22); /* cbSize */
+
+ Stream_Write_UINT16(s, open->ExtraFormatData->Samples.wReserved);
+ Stream_Write_UINT32(s, open->ExtraFormatData->dwChannelMask);
+
+ Stream_Write_UINT32(s, open->ExtraFormatData->SubFormat.Data1);
+ Stream_Write_UINT16(s, open->ExtraFormatData->SubFormat.Data2);
+ Stream_Write_UINT16(s, open->ExtraFormatData->SubFormat.Data3);
+ Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[0]);
+ Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[1]);
+ Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[2]);
+ Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[3]);
+ Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[4]);
+ Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[5]);
+ Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[6]);
+ Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[7]);
+ }
+ else
+ {
+ WINPR_ASSERT(open->captureFormat.wFormatTag != WAVE_FORMAT_EXTENSIBLE);
+
+ Stream_Write_UINT16(s, 0); /* cbSize */
+ }
+
+ return audin_server_packet_send(context, s);
+}
+
+static UINT audin_server_send_format_change(audin_server_context* context,
+ const SNDIN_FORMATCHANGE* format_change)
+{
+ audin_server* audin = (audin_server*)context;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(format_change);
+
+ wStream* s = audin_server_packet_new(audin->log, 4, MSG_SNDIN_FORMATCHANGE);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT32(s, format_change->NewFormat);
+
+ return audin_server_packet_send(context, s);
+}
+
+static UINT audin_server_receive_version_default(audin_server_context* audin_ctx,
+ const SNDIN_VERSION* version)
+{
+ audin_server* audin = (audin_server*)audin_ctx;
+ SNDIN_FORMATS formats = { 0 };
+
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(version);
+
+ if (version->Version == 0)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "Received invalid AUDIO_INPUT version from client");
+ return ERROR_INVALID_DATA;
+ }
+
+ WLog_Print(audin->log, WLOG_DEBUG, "AUDIO_INPUT version of client: %u", version->Version);
+
+ formats.NumFormats = audin->audin_n_server_formats;
+ formats.SoundFormats = audin->audin_server_formats;
+
+ return audin->context.SendFormats(&audin->context, &formats);
+}
+
+static UINT send_open(audin_server* audin)
+{
+ SNDIN_OPEN open = { 0 };
+
+ WINPR_ASSERT(audin);
+
+ open.FramesPerPacket = 441;
+ open.initialFormat = audin->audin_client_format_idx;
+ open.captureFormat.wFormatTag = WAVE_FORMAT_PCM;
+ open.captureFormat.nChannels = 2;
+ open.captureFormat.nSamplesPerSec = 44100;
+ open.captureFormat.nAvgBytesPerSec = 44100 * 2 * 2;
+ open.captureFormat.nBlockAlign = 4;
+ open.captureFormat.wBitsPerSample = 16;
+
+ WINPR_ASSERT(audin->context.SendOpen);
+ return audin->context.SendOpen(&audin->context, &open);
+}
+
+static UINT audin_server_receive_formats_default(audin_server_context* context,
+ const SNDIN_FORMATS* formats)
+{
+ audin_server* audin = (audin_server*)context;
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(formats);
+
+ if (audin->audin_negotiated_format)
+ {
+ WLog_Print(audin->log, WLOG_ERROR,
+ "Received client formats, but negotiation was already done");
+ return ERROR_INVALID_DATA;
+ }
+
+ for (UINT32 i = 0; i < audin->audin_n_server_formats; ++i)
+ {
+ for (UINT32 j = 0; j < formats->NumFormats; ++j)
+ {
+ if (audio_format_compatible(&audin->audin_server_formats[i], &formats->SoundFormats[j]))
+ {
+ audin->audin_negotiated_format = &audin->audin_server_formats[i];
+ audin->audin_client_format_idx = i;
+ return send_open(audin);
+ }
+ }
+ }
+
+ WLog_Print(audin->log, WLOG_ERROR, "Could not agree on a audio format with the server");
+
+ return ERROR_INVALID_DATA;
+}
+
+static UINT audin_server_receive_format_change_default(audin_server_context* context,
+ const SNDIN_FORMATCHANGE* format_change)
+{
+ audin_server* audin = (audin_server*)context;
+
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(format_change);
+
+ if (format_change->NewFormat != audin->audin_client_format_idx)
+ {
+ WLog_Print(audin->log, WLOG_ERROR,
+ "NewFormat in FormatChange differs from requested format");
+ return ERROR_INVALID_DATA;
+ }
+
+ WLog_Print(audin->log, WLOG_DEBUG, "Received Format Change PDU: %u", format_change->NewFormat);
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT audin_server_incoming_data_default(audin_server_context* context,
+ const SNDIN_DATA_INCOMING* data_incoming)
+{
+ audin_server* audin = (audin_server*)context;
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(data_incoming);
+
+ /* TODO: Implement bandwidth measure of clients uplink */
+ WLog_Print(audin->log, WLOG_DEBUG, "Received Incoming Data PDU");
+ return CHANNEL_RC_OK;
+}
+
+static UINT audin_server_open_reply_default(audin_server_context* context,
+ const SNDIN_OPEN_REPLY* open_reply)
+{
+ audin_server* audin = (audin_server*)context;
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(open_reply);
+
+ /* TODO: Implement failure handling */
+ WLog_Print(audin->log, WLOG_DEBUG, "Open Reply PDU: Result: %i", open_reply->Result);
+ return CHANNEL_RC_OK;
+}
+
+audin_server_context* audin_server_context_new(HANDLE vcm)
+{
+ audin_server* audin = (audin_server*)calloc(1, sizeof(audin_server));
+
+ if (!audin)
+ {
+ WLog_ERR(AUDIN_TAG, "calloc failed!");
+ return NULL;
+ }
+ audin->log = WLog_Get(AUDIN_TAG);
+ audin->context.vcm = vcm;
+ audin->context.Open = audin_server_open;
+ audin->context.IsOpen = audin_server_is_open;
+ audin->context.Close = audin_server_close;
+
+ audin->context.SendVersion = audin_server_send_version;
+ audin->context.SendFormats = audin_server_send_formats;
+ audin->context.SendOpen = audin_server_send_open;
+ audin->context.SendFormatChange = audin_server_send_format_change;
+
+ /* Default values */
+ audin->context.serverVersion = SNDIN_VERSION_Version_2;
+ audin->context.ReceiveVersion = audin_server_receive_version_default;
+ audin->context.ReceiveFormats = audin_server_receive_formats_default;
+ audin->context.ReceiveFormatChange = audin_server_receive_format_change_default;
+ audin->context.IncomingData = audin_server_incoming_data_default;
+ audin->context.OpenReply = audin_server_open_reply_default;
+
+ return &audin->context;
+}
+
+void audin_server_context_free(audin_server_context* context)
+{
+ audin_server* audin = (audin_server*)context;
+
+ if (!audin)
+ return;
+
+ audin_server_close(context);
+ audio_formats_free(audin->audin_server_formats, audin->audin_n_server_formats);
+ audin->audin_server_formats = NULL;
+ free(audin);
+}
+
+BOOL audin_server_set_formats(audin_server_context* context, SSIZE_T count,
+ const AUDIO_FORMAT* formats)
+{
+ audin_server* audin = (audin_server*)context;
+ WINPR_ASSERT(audin);
+
+ audio_formats_free(audin->audin_server_formats, audin->audin_n_server_formats);
+ audin->audin_n_server_formats = 0;
+ audin->audin_server_formats = NULL;
+ audin->audin_negotiated_format = NULL;
+
+ if (count < 0)
+ {
+ const size_t audin_n_server_formats =
+ server_audin_get_formats(&audin->audin_server_formats);
+ WINPR_ASSERT(audin_n_server_formats <= UINT32_MAX);
+
+ audin->audin_n_server_formats = (UINT32)audin_n_server_formats;
+ }
+ else
+ {
+ AUDIO_FORMAT* audin_server_formats = audio_formats_new(count);
+ if (!audin_server_formats)
+ return count == 0;
+
+ for (SSIZE_T x = 0; x < count; x++)
+ {
+ if (!audio_format_copy(&formats[x], &audin_server_formats[x]))
+ {
+ audio_formats_free(audin_server_formats, count);
+ return FALSE;
+ }
+ }
+
+ WINPR_ASSERT(count <= UINT32_MAX);
+ audin->audin_server_formats = audin_server_formats;
+ audin->audin_n_server_formats = (UINT32)count;
+ }
+ return audin->audin_n_server_formats > 0;
+}
+
+const AUDIO_FORMAT* audin_server_get_negotiated_format(const audin_server_context* context)
+{
+ const audin_server* audin = (const audin_server*)context;
+ WINPR_ASSERT(audin);
+
+ return audin->audin_negotiated_format;
+}
diff --git a/channels/client/CMakeLists.txt b/channels/client/CMakeLists.txt
new file mode 100644
index 0000000..923683a
--- /dev/null
+++ b/channels/client/CMakeLists.txt
@@ -0,0 +1,125 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set(MODULE_NAME "freerdp-channels-client")
+set(MODULE_PREFIX "FREERDP_CHANNELS_CLIENT")
+
+set(${MODULE_PREFIX}_SRCS
+ ${CMAKE_CURRENT_BINARY_DIR}/tables.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/tables.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/addin.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/addin.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/generic_dynvc.c)
+
+if(CHANNEL_STATIC_CLIENT_ENTRIES)
+ list(REMOVE_DUPLICATES CHANNEL_STATIC_CLIENT_ENTRIES)
+endif()
+
+set(CLIENT_STATIC_TYPEDEFS "#if __GNUC__\n")
+set(CLIENT_STATIC_TYPEDEFS "${CLIENT_STATIC_TYPEDEFS}#pragma GCC diagnostic push\n")
+set(CLIENT_STATIC_TYPEDEFS "${CLIENT_STATIC_TYPEDEFS}#pragma GCC diagnostic ignored \"-Wstrict-prototypes\"\n")
+set(CLIENT_STATIC_TYPEDEFS "${CLIENT_STATIC_TYPEDEFS}#endif\n")
+set(CLIENT_STATIC_TYPEDEFS "${CLIENT_STATIC_TYPEDEFS}typedef UINT (*static_entry_fkt)();\n")
+set(CLIENT_STATIC_TYPEDEFS "${CLIENT_STATIC_TYPEDEFS}typedef UINT (*static_addin_fkt)();\n")
+set(CLIENT_STATIC_TYPEDEFS "${CLIENT_STATIC_TYPEDEFS}#if __GNUC__\n")
+ set(CLIENT_STATIC_TYPEDEFS "${CLIENT_STATIC_TYPEDEFS}#pragma GCC diagnostic pop\n")
+ set(CLIENT_STATIC_TYPEDEFS "${CLIENT_STATIC_TYPEDEFS}#endif\n")
+
+foreach(STATIC_ENTRY ${CHANNEL_STATIC_CLIENT_ENTRIES})
+ foreach(STATIC_MODULE ${CHANNEL_STATIC_CLIENT_MODULES})
+ foreach(ENTRY ${${STATIC_MODULE}_CLIENT_ENTRY})
+ if(${ENTRY} STREQUAL ${STATIC_ENTRY})
+ set(STATIC_MODULE_NAME ${${STATIC_MODULE}_CLIENT_NAME})
+ set(STATIC_MODULE_CHANNEL ${${STATIC_MODULE}_CLIENT_CHANNEL})
+ set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${STATIC_MODULE_NAME})
+
+ set(ENTRY_POINT_NAME "${STATIC_MODULE_CHANNEL}_${ENTRY}")
+ if(${ENTRY} STREQUAL "VirtualChannelEntry")
+ set(ENTRY_POINT_IMPORT "extern BOOL VCAPITYPE ${ENTRY_POINT_NAME}(PCHANNEL_ENTRY_POINTS);")
+ elseif(${ENTRY} STREQUAL "VirtualChannelEntryEx")
+ set(ENTRY_POINT_IMPORT "extern BOOL VCAPITYPE ${ENTRY_POINT_NAME}(PCHANNEL_ENTRY_POINTS,PVOID);")
+ elseif(${ENTRY} MATCHES "DVCPluginEntry$")
+ set(ENTRY_POINT_IMPORT "extern UINT ${ENTRY_POINT_NAME}(IDRDYNVC_ENTRY_POINTS* pEntryPoints);")
+ elseif(${ENTRY} MATCHES "DeviceServiceEntry$")
+ set(ENTRY_POINT_IMPORT "extern UINT ${ENTRY_POINT_NAME}(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints);")
+ else()
+ set(ENTRY_POINT_IMPORT "extern UINT ${ENTRY_POINT_NAME}(void);")
+ endif()
+ set(${STATIC_ENTRY}_IMPORTS "${${STATIC_ENTRY}_IMPORTS}\n${ENTRY_POINT_IMPORT}")
+ set(${STATIC_ENTRY}_TABLE "${${STATIC_ENTRY}_TABLE}\n\t{ \"${STATIC_MODULE_CHANNEL}\", (static_entry_fkt)${ENTRY_POINT_NAME} },")
+ endif()
+ endforeach()
+ endforeach()
+endforeach()
+
+set(CLIENT_STATIC_ENTRY_TABLES_LIST "${CLIENT_STATIC_ENTRY_TABLES_LIST}\nextern const STATIC_ENTRY_TABLE CLIENT_STATIC_ENTRY_TABLES[];\nconst STATIC_ENTRY_TABLE CLIENT_STATIC_ENTRY_TABLES[] =\n{")
+
+foreach(STATIC_ENTRY ${CHANNEL_STATIC_CLIENT_ENTRIES})
+ set(CLIENT_STATIC_ENTRY_IMPORTS "${CLIENT_STATIC_ENTRY_IMPORTS}\n${${STATIC_ENTRY}_IMPORTS}")
+ set(CLIENT_STATIC_ENTRY_TABLES "${CLIENT_STATIC_ENTRY_TABLES}\nextern const STATIC_ENTRY CLIENT_${STATIC_ENTRY}_TABLE[];\nconst STATIC_ENTRY CLIENT_${STATIC_ENTRY}_TABLE[] =\n{")
+ set(CLIENT_STATIC_ENTRY_TABLES "${CLIENT_STATIC_ENTRY_TABLES}\n${${STATIC_ENTRY}_TABLE}")
+ set(CLIENT_STATIC_ENTRY_TABLES "${CLIENT_STATIC_ENTRY_TABLES}\n\t{ NULL, NULL }\n};")
+ set(CLIENT_STATIC_ENTRY_TABLES_LIST "${CLIENT_STATIC_ENTRY_TABLES_LIST}\n\t{ \"${STATIC_ENTRY}\", CLIENT_${STATIC_ENTRY}_TABLE },")
+endforeach()
+
+set(CLIENT_STATIC_ENTRY_TABLES_LIST "${CLIENT_STATIC_ENTRY_TABLES_LIST}\n\t{ NULL, NULL }\n};")
+
+set(CLIENT_STATIC_ADDIN_TABLE "extern const STATIC_ADDIN_TABLE CLIENT_STATIC_ADDIN_TABLE[];\nconst STATIC_ADDIN_TABLE CLIENT_STATIC_ADDIN_TABLE[] =\n{")
+foreach(STATIC_MODULE ${CHANNEL_STATIC_CLIENT_MODULES})
+ set(STATIC_MODULE_NAME ${${STATIC_MODULE}_CLIENT_NAME})
+ set(STATIC_MODULE_CHANNEL ${${STATIC_MODULE}_CLIENT_CHANNEL})
+ string(TOUPPER "CLIENT_${STATIC_MODULE_CHANNEL}_SUBSYSTEM_TABLE" SUBSYSTEM_TABLE_NAME)
+ set(SUBSYSTEM_TABLE "extern const STATIC_SUBSYSTEM_ENTRY ${SUBSYSTEM_TABLE_NAME}[];\nconst STATIC_SUBSYSTEM_ENTRY ${SUBSYSTEM_TABLE_NAME}[] =\n{")
+ get_target_property(CHANNEL_SUBSYSTEMS ${STATIC_MODULE_NAME} SUBSYSTEMS)
+ if(CHANNEL_SUBSYSTEMS MATCHES "NOTFOUND")
+ set(CHANNEL_SUBSYSTEMS "")
+ endif()
+ foreach(STATIC_SUBSYSTEM ${CHANNEL_SUBSYSTEMS})
+ if(${STATIC_SUBSYSTEM} MATCHES "^([^-]*)-(.*)")
+ string(REGEX REPLACE "^([^-]*)-(.*)" "\\1" STATIC_SUBSYSTEM_NAME ${STATIC_SUBSYSTEM})
+ string(REGEX REPLACE "^([^-]*)-(.*)" "\\2" STATIC_SUBSYSTEM_TYPE ${STATIC_SUBSYSTEM})
+ else()
+ set(STATIC_SUBSYSTEM_NAME "${STATIC_SUBSYSTEM}")
+ set(STATIC_SUBSYSTEM_TYPE "")
+ endif()
+ string(LENGTH "${STATIC_SUBSYSTEM_TYPE}" _type_length)
+ set(SUBSYSTEM_MODULE_NAME "${STATIC_MODULE_NAME}-${STATIC_SUBSYSTEM}")
+ set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${SUBSYSTEM_MODULE_NAME})
+ if(_type_length GREATER 0)
+ set(STATIC_SUBSYSTEM_ENTRY "${STATIC_SUBSYSTEM_NAME}_freerdp_${STATIC_MODULE_CHANNEL}_client_${STATIC_SUBSYSTEM_TYPE}_subsystem_entry")
+ else()
+ set(STATIC_SUBSYSTEM_ENTRY "${STATIC_SUBSYSTEM_NAME}_freerdp_${STATIC_MODULE_CHANNEL}_client_subsystem_entry")
+ endif()
+ set(SUBSYSTEM_TABLE "${SUBSYSTEM_TABLE}\n\t{ \"${STATIC_SUBSYSTEM_NAME}\", \"${STATIC_SUBSYSTEM_TYPE}\", ${STATIC_SUBSYSTEM_ENTRY} },")
+ set(SUBSYSTEM_IMPORT "extern UINT ${STATIC_SUBSYSTEM_ENTRY}(void*);")
+ set(CLIENT_STATIC_SUBSYSTEM_IMPORTS "${CLIENT_STATIC_SUBSYSTEM_IMPORTS}\n${SUBSYSTEM_IMPORT}")
+ endforeach()
+ set(SUBSYSTEM_TABLE "${SUBSYSTEM_TABLE}\n\t{ NULL, NULL, NULL }\n};")
+ set(CLIENT_STATIC_SUBSYSTEM_TABLES "${CLIENT_STATIC_SUBSYSTEM_TABLES}\n${SUBSYSTEM_TABLE}")
+ foreach(ENTRY ${${STATIC_MODULE}_CLIENT_ENTRY})
+ set (ENTRY_POINT_NAME ${STATIC_MODULE_CHANNEL}_${ENTRY})
+ set(CLIENT_STATIC_ADDIN_TABLE "${CLIENT_STATIC_ADDIN_TABLE}\n\t{ \"${STATIC_MODULE_CHANNEL}\", \"${ENTRY}\", (static_addin_fkt)${ENTRY_POINT_NAME}, ${SUBSYSTEM_TABLE_NAME} },")
+ endforeach()
+endforeach()
+set(CLIENT_STATIC_ADDIN_TABLE "${CLIENT_STATIC_ADDIN_TABLE}\n\t{ NULL, NULL, NULL, NULL }\n};")
+
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tables.c.in ${CMAKE_CURRENT_BINARY_DIR}/tables.c)
+
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp winpr)
+
+set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} PARENT_SCOPE)
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} PARENT_SCOPE)
diff --git a/channels/client/addin.c b/channels/client/addin.c
new file mode 100644
index 0000000..6d87f6c
--- /dev/null
+++ b/channels/client/addin.c
@@ -0,0 +1,761 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Channel Addins
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/path.h>
+#include <winpr/string.h>
+#include <winpr/file.h>
+#include <winpr/synch.h>
+#include <winpr/library.h>
+#include <winpr/collections.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/addin.h>
+#include <freerdp/build-config.h>
+#include <freerdp/client/channels.h>
+
+#include "tables.h"
+
+#include "addin.h"
+
+#include <freerdp/channels/log.h>
+#define TAG CHANNELS_TAG("addin")
+
+extern const STATIC_ENTRY_TABLE CLIENT_STATIC_ENTRY_TABLES[];
+
+static void* freerdp_channels_find_static_entry_in_table(const STATIC_ENTRY_TABLE* table,
+ const char* identifier)
+{
+ size_t index = 0;
+ const STATIC_ENTRY* pEntry = (const STATIC_ENTRY*)&table->table[index++];
+
+ while (pEntry->entry != NULL)
+ {
+ if (strcmp(pEntry->name, identifier) == 0)
+ {
+ return (void*)pEntry->entry;
+ }
+
+ pEntry = (const STATIC_ENTRY*)&table->table[index++];
+ }
+
+ return NULL;
+}
+
+void* freerdp_channels_client_find_static_entry(const char* name, const char* identifier)
+{
+ size_t index = 0;
+ const STATIC_ENTRY_TABLE* pEntry = &CLIENT_STATIC_ENTRY_TABLES[index++];
+
+ while (pEntry->table != NULL)
+ {
+ if (strcmp(pEntry->name, name) == 0)
+ {
+ return freerdp_channels_find_static_entry_in_table(pEntry, identifier);
+ }
+
+ pEntry = &CLIENT_STATIC_ENTRY_TABLES[index++];
+ }
+
+ return NULL;
+}
+
+extern const STATIC_ADDIN_TABLE CLIENT_STATIC_ADDIN_TABLE[];
+
+static FREERDP_ADDIN** freerdp_channels_list_client_static_addins(LPCSTR pszName,
+ LPCSTR pszSubsystem,
+ LPCSTR pszType, DWORD dwFlags)
+{
+ DWORD nAddins = 0;
+ FREERDP_ADDIN** ppAddins = NULL;
+ const STATIC_SUBSYSTEM_ENTRY* subsystems = NULL;
+ nAddins = 0;
+ ppAddins = (FREERDP_ADDIN**)calloc(128, sizeof(FREERDP_ADDIN*));
+
+ if (!ppAddins)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return NULL;
+ }
+
+ ppAddins[nAddins] = NULL;
+
+ for (size_t i = 0; CLIENT_STATIC_ADDIN_TABLE[i].name != NULL; i++)
+ {
+ FREERDP_ADDIN* pAddin = (FREERDP_ADDIN*)calloc(1, sizeof(FREERDP_ADDIN));
+
+ if (!pAddin)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ goto error_out;
+ }
+
+ sprintf_s(pAddin->cName, ARRAYSIZE(pAddin->cName), "%s", CLIENT_STATIC_ADDIN_TABLE[i].name);
+ pAddin->dwFlags = FREERDP_ADDIN_CLIENT;
+ pAddin->dwFlags |= FREERDP_ADDIN_STATIC;
+ pAddin->dwFlags |= FREERDP_ADDIN_NAME;
+ ppAddins[nAddins++] = pAddin;
+ subsystems = (const STATIC_SUBSYSTEM_ENTRY*)CLIENT_STATIC_ADDIN_TABLE[i].table;
+
+ for (size_t j = 0; subsystems[j].name != NULL; j++)
+ {
+ pAddin = (FREERDP_ADDIN*)calloc(1, sizeof(FREERDP_ADDIN));
+
+ if (!pAddin)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ goto error_out;
+ }
+
+ sprintf_s(pAddin->cName, ARRAYSIZE(pAddin->cName), "%s",
+ CLIENT_STATIC_ADDIN_TABLE[i].name);
+ sprintf_s(pAddin->cSubsystem, ARRAYSIZE(pAddin->cSubsystem), "%s", subsystems[j].name);
+ pAddin->dwFlags = FREERDP_ADDIN_CLIENT;
+ pAddin->dwFlags |= FREERDP_ADDIN_STATIC;
+ pAddin->dwFlags |= FREERDP_ADDIN_NAME;
+ pAddin->dwFlags |= FREERDP_ADDIN_SUBSYSTEM;
+ ppAddins[nAddins++] = pAddin;
+ }
+ }
+
+ return ppAddins;
+error_out:
+ freerdp_channels_addin_list_free(ppAddins);
+ return NULL;
+}
+
+static HANDLE FindFirstFileUTF8(LPCSTR pszSearchPath, WIN32_FIND_DATAW* FindData)
+{
+ HANDLE hdl = INVALID_HANDLE_VALUE;
+ if (!pszSearchPath)
+ return hdl;
+ WCHAR* wpath = ConvertUtf8ToWCharAlloc(pszSearchPath, NULL);
+ if (!wpath)
+ return hdl;
+
+ hdl = FindFirstFileW(wpath, FindData);
+ free(wpath);
+
+ return hdl;
+}
+
+static FREERDP_ADDIN** freerdp_channels_list_dynamic_addins(LPCSTR pszName, LPCSTR pszSubsystem,
+ LPCSTR pszType, DWORD dwFlags)
+{
+ int nDashes = 0;
+ HANDLE hFind = NULL;
+ DWORD nAddins = 0;
+ LPSTR pszPattern = NULL;
+ size_t cchPattern = 0;
+ LPCSTR pszAddinPath = FREERDP_ADDIN_PATH;
+ LPCSTR pszInstallPrefix = FREERDP_INSTALL_PREFIX;
+ LPCSTR pszExtension = NULL;
+ LPSTR pszSearchPath = NULL;
+ size_t cchSearchPath = 0;
+ size_t cchAddinPath = 0;
+ size_t cchInstallPrefix = 0;
+ FREERDP_ADDIN** ppAddins = NULL;
+ WIN32_FIND_DATAW FindData = { 0 };
+ cchAddinPath = strnlen(pszAddinPath, sizeof(FREERDP_ADDIN_PATH));
+ cchInstallPrefix = strnlen(pszInstallPrefix, sizeof(FREERDP_INSTALL_PREFIX));
+ pszExtension = PathGetSharedLibraryExtensionA(0);
+ cchPattern = 128 + strnlen(pszExtension, MAX_PATH) + 2;
+ pszPattern = (LPSTR)malloc(cchPattern + 1);
+
+ if (!pszPattern)
+ {
+ WLog_ERR(TAG, "malloc failed!");
+ return NULL;
+ }
+
+ if (pszName && pszSubsystem && pszType)
+ {
+ sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX "%s-client-%s-%s.%s",
+ pszName, pszSubsystem, pszType, pszExtension);
+ }
+ else if (pszName && pszType)
+ {
+ sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX "%s-client-?-%s.%s",
+ pszName, pszType, pszExtension);
+ }
+ else if (pszName)
+ {
+ sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX "%s-client*.%s", pszName,
+ pszExtension);
+ }
+ else
+ {
+ sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX "?-client*.%s",
+ pszExtension);
+ }
+
+ cchPattern = strnlen(pszPattern, cchPattern);
+ cchSearchPath = cchInstallPrefix + cchAddinPath + cchPattern + 3;
+ pszSearchPath = (LPSTR)calloc(cchSearchPath + 1, sizeof(char));
+
+ if (!pszSearchPath)
+ {
+ WLog_ERR(TAG, "malloc failed!");
+ free(pszPattern);
+ return NULL;
+ }
+
+ CopyMemory(pszSearchPath, pszInstallPrefix, cchInstallPrefix);
+ pszSearchPath[cchInstallPrefix] = '\0';
+ const HRESULT hr1 = NativePathCchAppendA(pszSearchPath, cchSearchPath + 1, pszAddinPath);
+ const HRESULT hr2 = NativePathCchAppendA(pszSearchPath, cchSearchPath + 1, pszPattern);
+ free(pszPattern);
+
+ if (FAILED(hr1) || FAILED(hr2))
+ {
+ free(pszSearchPath);
+ return NULL;
+ }
+
+ hFind = FindFirstFileUTF8(pszSearchPath, &FindData);
+
+ free(pszSearchPath);
+ nAddins = 0;
+ ppAddins = (FREERDP_ADDIN**)calloc(128, sizeof(FREERDP_ADDIN*));
+
+ if (!ppAddins)
+ {
+ FindClose(hFind);
+ WLog_ERR(TAG, "calloc failed!");
+ return NULL;
+ }
+
+ if (hFind == INVALID_HANDLE_VALUE)
+ return ppAddins;
+
+ do
+ {
+ char* cFileName = NULL;
+ BOOL used = FALSE;
+ FREERDP_ADDIN* pAddin = (FREERDP_ADDIN*)calloc(1, sizeof(FREERDP_ADDIN));
+
+ if (!pAddin)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ goto error_out;
+ }
+
+ cFileName =
+ ConvertWCharNToUtf8Alloc(FindData.cFileName, ARRAYSIZE(FindData.cFileName), NULL);
+ if (!cFileName)
+ goto skip;
+
+ nDashes = 0;
+ for (size_t index = 0; cFileName[index]; index++)
+ nDashes += (cFileName[index] == '-') ? 1 : 0;
+
+ if (nDashes == 1)
+ {
+ size_t len = 0;
+ char* p[2] = { 0 };
+ /* <name>-client.<extension> */
+ p[0] = cFileName;
+ p[1] = strchr(p[0], '-');
+ if (!p[1])
+ goto skip;
+ p[1] += 1;
+
+ len = (size_t)(p[1] - p[0]);
+ if (len < 1)
+ {
+ WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName);
+ goto skip;
+ }
+ strncpy(pAddin->cName, p[0], MIN(ARRAYSIZE(pAddin->cName), len - 1));
+
+ pAddin->dwFlags = FREERDP_ADDIN_CLIENT;
+ pAddin->dwFlags |= FREERDP_ADDIN_DYNAMIC;
+ pAddin->dwFlags |= FREERDP_ADDIN_NAME;
+ ppAddins[nAddins++] = pAddin;
+
+ used = TRUE;
+ }
+ else if (nDashes == 2)
+ {
+ size_t len = 0;
+ char* p[4] = { 0 };
+ /* <name>-client-<subsystem>.<extension> */
+ p[0] = cFileName;
+ p[1] = strchr(p[0], '-');
+ if (!p[1])
+ goto skip;
+ p[1] += 1;
+ p[2] = strchr(p[1], '-');
+ if (!p[2])
+ goto skip;
+ p[2] += 1;
+ p[3] = strchr(p[2], '.');
+ if (!p[3])
+ goto skip;
+ p[3] += 1;
+
+ len = (size_t)(p[1] - p[0]);
+ if (len < 1)
+ {
+ WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName);
+ goto skip;
+ }
+ strncpy(pAddin->cName, p[0], MIN(ARRAYSIZE(pAddin->cName), len - 1));
+
+ len = (size_t)(p[3] - p[2]);
+ if (len < 1)
+ {
+ WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName);
+ goto skip;
+ }
+ strncpy(pAddin->cSubsystem, p[2], MIN(ARRAYSIZE(pAddin->cSubsystem), len - 1));
+
+ pAddin->dwFlags = FREERDP_ADDIN_CLIENT;
+ pAddin->dwFlags |= FREERDP_ADDIN_DYNAMIC;
+ pAddin->dwFlags |= FREERDP_ADDIN_NAME;
+ pAddin->dwFlags |= FREERDP_ADDIN_SUBSYSTEM;
+ ppAddins[nAddins++] = pAddin;
+
+ used = TRUE;
+ }
+ else if (nDashes == 3)
+ {
+ size_t len = 0;
+ char* p[5] = { 0 };
+ /* <name>-client-<subsystem>-<type>.<extension> */
+ p[0] = cFileName;
+ p[1] = strchr(p[0], '-');
+ if (!p[1])
+ goto skip;
+ p[1] += 1;
+ p[2] = strchr(p[1], '-');
+ if (!p[2])
+ goto skip;
+ p[2] += 1;
+ p[3] = strchr(p[2], '-');
+ if (!p[3])
+ goto skip;
+ p[3] += 1;
+ p[4] = strchr(p[3], '.');
+ if (!p[4])
+ goto skip;
+ p[4] += 1;
+
+ len = (size_t)(p[1] - p[0]);
+ if (len < 1)
+ {
+ WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName);
+ goto skip;
+ }
+ strncpy(pAddin->cName, p[0], MIN(ARRAYSIZE(pAddin->cName), len - 1));
+
+ len = (size_t)(p[3] - p[2]);
+ if (len < 1)
+ {
+ WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName);
+ goto skip;
+ }
+ strncpy(pAddin->cSubsystem, p[2], MIN(ARRAYSIZE(pAddin->cSubsystem), len - 1));
+
+ len = (size_t)(p[4] - p[3]);
+ if (len < 1)
+ {
+ WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName);
+ goto skip;
+ }
+ strncpy(pAddin->cType, p[3], MIN(ARRAYSIZE(pAddin->cType), len - 1));
+
+ pAddin->dwFlags = FREERDP_ADDIN_CLIENT;
+ pAddin->dwFlags |= FREERDP_ADDIN_DYNAMIC;
+ pAddin->dwFlags |= FREERDP_ADDIN_NAME;
+ pAddin->dwFlags |= FREERDP_ADDIN_SUBSYSTEM;
+ pAddin->dwFlags |= FREERDP_ADDIN_TYPE;
+ ppAddins[nAddins++] = pAddin;
+
+ used = TRUE;
+ }
+
+ skip:
+ free(cFileName);
+ if (!used)
+ free(pAddin);
+
+ } while (FindNextFileW(hFind, &FindData));
+
+ FindClose(hFind);
+ ppAddins[nAddins] = NULL;
+ return ppAddins;
+error_out:
+ FindClose(hFind);
+ freerdp_channels_addin_list_free(ppAddins);
+ return NULL;
+}
+
+FREERDP_ADDIN** freerdp_channels_list_addins(LPCSTR pszName, LPCSTR pszSubsystem, LPCSTR pszType,
+ DWORD dwFlags)
+{
+ if (dwFlags & FREERDP_ADDIN_STATIC)
+ return freerdp_channels_list_client_static_addins(pszName, pszSubsystem, pszType, dwFlags);
+ else if (dwFlags & FREERDP_ADDIN_DYNAMIC)
+ return freerdp_channels_list_dynamic_addins(pszName, pszSubsystem, pszType, dwFlags);
+
+ return NULL;
+}
+
+void freerdp_channels_addin_list_free(FREERDP_ADDIN** ppAddins)
+{
+ if (!ppAddins)
+ return;
+
+ for (size_t index = 0; ppAddins[index] != NULL; index++)
+ free(ppAddins[index]);
+
+ free(ppAddins);
+}
+
+extern const STATIC_ENTRY CLIENT_VirtualChannelEntryEx_TABLE[];
+
+static BOOL freerdp_channels_is_virtual_channel_entry_ex(LPCSTR pszName)
+{
+ for (size_t i = 0; CLIENT_VirtualChannelEntryEx_TABLE[i].name != NULL; i++)
+ {
+ const STATIC_ENTRY* entry = &CLIENT_VirtualChannelEntryEx_TABLE[i];
+
+ if (!strncmp(entry->name, pszName, MAX_PATH))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+PVIRTUALCHANNELENTRY freerdp_channels_load_static_addin_entry(LPCSTR pszName, LPCSTR pszSubsystem,
+ LPCSTR pszType, DWORD dwFlags)
+{
+ const STATIC_ADDIN_TABLE* table = CLIENT_STATIC_ADDIN_TABLE;
+ const char* type = NULL;
+
+ if (!pszName)
+ return NULL;
+
+ if (dwFlags & FREERDP_ADDIN_CHANNEL_DYNAMIC)
+ type = "DVCPluginEntry";
+ else if (dwFlags & FREERDP_ADDIN_CHANNEL_DEVICE)
+ type = "DeviceServiceEntry";
+ else if (dwFlags & FREERDP_ADDIN_CHANNEL_STATIC)
+ {
+ if (dwFlags & FREERDP_ADDIN_CHANNEL_ENTRYEX)
+ type = "VirtualChannelEntryEx";
+ else
+ type = "VirtualChannelEntry";
+ }
+
+ for (; table->name != NULL; table++)
+ {
+ if (strncmp(table->name, pszName, MAX_PATH) == 0)
+ {
+ if (type && strncmp(table->type, type, MAX_PATH))
+ continue;
+
+ if (pszSubsystem != NULL)
+ {
+ const STATIC_SUBSYSTEM_ENTRY* subsystems = table->table;
+
+ for (; subsystems->name != NULL; subsystems++)
+ {
+ /* If the pszSubsystem is an empty string use the default backend. */
+ if ((strnlen(pszSubsystem, 1) ==
+ 0) || /* we only want to know if strnlen is > 0 */
+ (strncmp(subsystems->name, pszSubsystem, MAX_PATH) == 0))
+ {
+ if (pszType)
+ {
+ if (strncmp(subsystems->type, pszType, MAX_PATH) == 0)
+ return (PVIRTUALCHANNELENTRY)subsystems->entry;
+ }
+ else
+ {
+ return (PVIRTUALCHANNELENTRY)subsystems->entry;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (dwFlags & FREERDP_ADDIN_CHANNEL_ENTRYEX)
+ {
+ if (!freerdp_channels_is_virtual_channel_entry_ex(pszName))
+ return NULL;
+ }
+
+ return (PVIRTUALCHANNELENTRY)table->entry;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+typedef struct
+{
+ wMessageQueue* queue;
+ wStream* data_in;
+ HANDLE thread;
+ char* channel_name;
+ rdpContext* ctx;
+ LPVOID userdata;
+ MsgHandler msg_handler;
+} msg_proc_internals;
+
+static DWORD WINAPI channel_client_thread_proc(LPVOID userdata)
+{
+ UINT error = CHANNEL_RC_OK;
+ wStream* data = NULL;
+ wMessage message = { 0 };
+ msg_proc_internals* internals = userdata;
+
+ WINPR_ASSERT(internals);
+
+ while (1)
+ {
+ if (!MessageQueue_Wait(internals->queue))
+ {
+ WLog_ERR(TAG, "MessageQueue_Wait failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+ if (!MessageQueue_Peek(internals->queue, &message, TRUE))
+ {
+ WLog_ERR(TAG, "MessageQueue_Peek failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (message.id == WMQ_QUIT)
+ break;
+
+ if (message.id == 0)
+ {
+ data = (wStream*)message.wParam;
+
+ if ((error = internals->msg_handler(internals->userdata, data)))
+ {
+ WLog_ERR(TAG, "msg_handler failed with error %" PRIu32 "!", error);
+ break;
+ }
+ }
+ }
+ if (error && internals->ctx)
+ {
+ char msg[128];
+ _snprintf(msg, 127,
+ "%s_virtual_channel_client_thread reported an"
+ " error",
+ internals->channel_name);
+ setChannelError(internals->ctx, error, msg);
+ }
+ ExitThread(error);
+ return error;
+}
+
+static void free_msg(void* obj)
+{
+ wMessage* msg = (wMessage*)obj;
+
+ if (msg && (msg->id == 0))
+ {
+ wStream* s = (wStream*)msg->wParam;
+ Stream_Free(s, TRUE);
+ }
+}
+
+static void channel_client_handler_free(msg_proc_internals* internals)
+{
+ if (!internals)
+ return;
+
+ if (internals->thread)
+ CloseHandle(internals->thread);
+ MessageQueue_Free(internals->queue);
+ Stream_Free(internals->data_in, TRUE);
+ free(internals->channel_name);
+ free(internals);
+}
+
+/* Create message queue and thread or not, depending on settings */
+void* channel_client_create_handler(rdpContext* ctx, LPVOID userdata, MsgHandler msg_handler,
+ const char* channel_name)
+{
+ msg_proc_internals* internals = calloc(1, sizeof(msg_proc_internals));
+ if (!internals)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return NULL;
+ }
+ internals->msg_handler = msg_handler;
+ internals->userdata = userdata;
+ if (channel_name)
+ {
+ internals->channel_name = _strdup(channel_name);
+ if (!internals->channel_name)
+ goto fail;
+ }
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(ctx->settings);
+ internals->ctx = ctx;
+ if ((freerdp_settings_get_uint32(ctx->settings, FreeRDP_ThreadingFlags) &
+ THREADING_FLAGS_DISABLE_THREADS) == 0)
+ {
+ wObject obj = { 0 };
+ obj.fnObjectFree = free_msg;
+ internals->queue = MessageQueue_New(&obj);
+ if (!internals->queue)
+ {
+ WLog_ERR(TAG, "MessageQueue_New failed!");
+ goto fail;
+ }
+
+ if (!(internals->thread =
+ CreateThread(NULL, 0, channel_client_thread_proc, (void*)internals, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ goto fail;
+ }
+ }
+ return internals;
+
+fail:
+ channel_client_handler_free(internals);
+ return NULL;
+}
+/* post a message in the queue or directly call the processing handler */
+UINT channel_client_post_message(void* MsgsHandle, LPVOID pData, UINT32 dataLength,
+ UINT32 totalLength, UINT32 dataFlags)
+{
+ msg_proc_internals* internals = MsgsHandle;
+ wStream* data_in = NULL;
+
+ if (!internals)
+ {
+ /* TODO: return some error here */
+ return CHANNEL_RC_OK;
+ }
+
+ if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME))
+ {
+ return CHANNEL_RC_OK;
+ }
+
+ if (dataFlags & CHANNEL_FLAG_FIRST)
+ {
+ if (internals->data_in)
+ {
+ if (!Stream_EnsureCapacity(internals->data_in, totalLength))
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ else
+ internals->data_in = Stream_New(NULL, totalLength);
+ }
+
+ if (!(data_in = internals->data_in))
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(data_in, dataLength))
+ {
+ Stream_Free(internals->data_in, TRUE);
+ internals->data_in = NULL;
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write(data_in, pData, dataLength);
+
+ if (dataFlags & CHANNEL_FLAG_LAST)
+ {
+ if (Stream_Capacity(data_in) != Stream_GetPosition(data_in))
+ {
+ char msg[128];
+ _snprintf(msg, 127, "%s_plugin_process_received: read error", internals->channel_name);
+ WLog_ERR(TAG, msg);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ internals->data_in = NULL;
+ Stream_SealLength(data_in);
+ Stream_SetPosition(data_in, 0);
+
+ if ((freerdp_settings_get_uint32(internals->ctx->settings, FreeRDP_ThreadingFlags) &
+ THREADING_FLAGS_DISABLE_THREADS) != 0)
+ {
+ UINT error = CHANNEL_RC_OK;
+ if ((error = internals->msg_handler(internals->userdata, data_in)))
+ {
+ WLog_ERR(TAG,
+ "msg_handler failed with error"
+ " %" PRIu32 "!",
+ error);
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+ else if (!MessageQueue_Post(internals->queue, NULL, 0, (void*)data_in, NULL))
+ {
+ WLog_ERR(TAG, "MessageQueue_Post failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+ return CHANNEL_RC_OK;
+}
+/* Tear down queue and thread */
+UINT channel_client_quit_handler(void* MsgsHandle)
+{
+ msg_proc_internals* internals = MsgsHandle;
+ UINT rc = 0;
+ if (!internals)
+ {
+ /* TODO: return some error here */
+ return CHANNEL_RC_OK;
+ }
+
+ WINPR_ASSERT(internals->ctx);
+ WINPR_ASSERT(internals->ctx->settings);
+
+ if ((freerdp_settings_get_uint32(internals->ctx->settings, FreeRDP_ThreadingFlags) &
+ THREADING_FLAGS_DISABLE_THREADS) == 0)
+ {
+ if (internals->queue && internals->thread)
+ {
+ if (MessageQueue_PostQuit(internals->queue, 0) &&
+ (WaitForSingleObject(internals->thread, INFINITE) == WAIT_FAILED))
+ {
+ rc = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", rc);
+ return rc;
+ }
+ }
+ }
+
+ channel_client_handler_free(internals);
+ return CHANNEL_RC_OK;
+}
diff --git a/channels/client/addin.h b/channels/client/addin.h
new file mode 100644
index 0000000..1b794e7
--- /dev/null
+++ b/channels/client/addin.h
@@ -0,0 +1,28 @@
+/*
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Channel Addins
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+typedef UINT (*MsgHandler)(LPVOID userdata, wStream* data);
+
+FREERDP_API void* channel_client_create_handler(rdpContext* ctx, LPVOID userdata,
+ MsgHandler handler, const char* channel_name);
+
+UINT channel_client_post_message(void* MsgsHandle, LPVOID pData, UINT32 dataLength,
+ UINT32 totalLength, UINT32 dataFlags);
+
+UINT channel_client_quit_handler(void* MsgsHandle);
diff --git a/channels/client/generic_dynvc.c b/channels/client/generic_dynvc.c
new file mode 100644
index 0000000..263b5ce
--- /dev/null
+++ b/channels/client/generic_dynvc.c
@@ -0,0 +1,212 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Dynamic channel
+ *
+ * Copyright 2022 David Fort <contact@hardening-consulting.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+#include <freerdp/log.h>
+#include <freerdp/client/channels.h>
+
+#define TAG FREERDP_TAG("genericdynvc")
+
+static UINT generic_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
+ IWTSVirtualChannel* pChannel, BYTE* Data,
+ BOOL* pbAccept,
+ IWTSVirtualChannelCallback** ppCallback)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = NULL;
+ GENERIC_DYNVC_PLUGIN* plugin = NULL;
+ GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback;
+
+ if (!listener_callback || !listener_callback->plugin)
+ return ERROR_INTERNAL_ERROR;
+
+ plugin = (GENERIC_DYNVC_PLUGIN*)listener_callback->plugin;
+ WLog_Print(plugin->log, WLOG_TRACE, "...");
+
+ callback = (GENERIC_CHANNEL_CALLBACK*)calloc(1, plugin->channelCallbackSize);
+ if (!callback)
+ {
+ WLog_Print(plugin->log, WLOG_ERROR, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ /* implant configured channel callbacks */
+ callback->iface = *plugin->channel_callbacks;
+
+ callback->plugin = listener_callback->plugin;
+ callback->channel_mgr = listener_callback->channel_mgr;
+ callback->channel = pChannel;
+
+ listener_callback->channel_callback = callback;
+ *ppCallback = (IWTSVirtualChannelCallback*)callback;
+ return CHANNEL_RC_OK;
+}
+
+static UINT generic_dynvc_plugin_initialize(IWTSPlugin* pPlugin,
+ IWTSVirtualChannelManager* pChannelMgr)
+{
+ UINT rc = 0;
+ GENERIC_LISTENER_CALLBACK* listener_callback = NULL;
+ GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin;
+
+ if (!plugin)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ if (!pChannelMgr)
+ return ERROR_INVALID_PARAMETER;
+
+ if (plugin->initialized)
+ {
+ WLog_ERR(TAG, "[%s] channel initialized twice, aborting", plugin->dynvc_name);
+ return ERROR_INVALID_DATA;
+ }
+
+ WLog_Print(plugin->log, WLOG_TRACE, "...");
+ listener_callback = (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK));
+ if (!listener_callback)
+ {
+ WLog_Print(plugin->log, WLOG_ERROR, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ plugin->listener_callback = listener_callback;
+ listener_callback->iface.OnNewChannelConnection = generic_on_new_channel_connection;
+ listener_callback->plugin = pPlugin;
+ listener_callback->channel_mgr = pChannelMgr;
+ rc = pChannelMgr->CreateListener(pChannelMgr, plugin->dynvc_name, 0, &listener_callback->iface,
+ &plugin->listener);
+
+ plugin->listener->pInterface = plugin->iface.pInterface;
+ plugin->initialized = (rc == CHANNEL_RC_OK);
+ return rc;
+}
+
+static UINT generic_plugin_terminated(IWTSPlugin* pPlugin)
+{
+ GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!plugin)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ WLog_Print(plugin->log, WLOG_TRACE, "...");
+
+ /* some channels (namely rdpei), look at initialized to see if they should continue to run */
+ plugin->initialized = FALSE;
+
+ if (plugin->terminatePluginFn)
+ plugin->terminatePluginFn(plugin);
+
+ if (plugin->listener_callback)
+ {
+ IWTSVirtualChannelManager* mgr = plugin->listener_callback->channel_mgr;
+ if (mgr)
+ IFCALL(mgr->DestroyListener, mgr, plugin->listener);
+ }
+
+ free(plugin->listener_callback);
+ free(plugin->dynvc_name);
+ free(plugin);
+ return error;
+}
+
+static UINT generic_dynvc_plugin_attached(IWTSPlugin* pPlugin)
+{
+ GENERIC_DYNVC_PLUGIN* pluginn = (GENERIC_DYNVC_PLUGIN*)pPlugin;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!pluginn)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ pluginn->attached = TRUE;
+ return error;
+}
+
+static UINT generic_dynvc_plugin_detached(IWTSPlugin* pPlugin)
+{
+ GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!plugin)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ plugin->attached = FALSE;
+ return error;
+}
+
+UINT freerdp_generic_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints, const char* logTag,
+ const char* name, size_t pluginSize, size_t channelCallbackSize,
+ const IWTSVirtualChannelCallback* channel_callbacks,
+ DYNVC_PLUGIN_INIT_FN initPluginFn,
+ DYNVC_PLUGIN_TERMINATE_FN terminatePluginFn)
+{
+ GENERIC_DYNVC_PLUGIN* plugin = NULL;
+ UINT error = CHANNEL_RC_INITIALIZATION_ERROR;
+
+ WINPR_ASSERT(pEntryPoints);
+ WINPR_ASSERT(pEntryPoints->GetPlugin);
+ WINPR_ASSERT(logTag);
+ WINPR_ASSERT(name);
+ WINPR_ASSERT(pluginSize >= sizeof(*plugin));
+ WINPR_ASSERT(channelCallbackSize >= sizeof(GENERIC_CHANNEL_CALLBACK));
+
+ plugin = (GENERIC_DYNVC_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, name);
+ if (plugin != NULL)
+ return CHANNEL_RC_ALREADY_INITIALIZED;
+
+ plugin = (GENERIC_DYNVC_PLUGIN*)calloc(1, pluginSize);
+ if (!plugin)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ plugin->log = WLog_Get(logTag);
+ plugin->attached = TRUE;
+ plugin->channel_callbacks = channel_callbacks;
+ plugin->channelCallbackSize = channelCallbackSize;
+ plugin->iface.Initialize = generic_dynvc_plugin_initialize;
+ plugin->iface.Connected = NULL;
+ plugin->iface.Disconnected = NULL;
+ plugin->iface.Terminated = generic_plugin_terminated;
+ plugin->iface.Attached = generic_dynvc_plugin_attached;
+ plugin->iface.Detached = generic_dynvc_plugin_detached;
+ plugin->terminatePluginFn = terminatePluginFn;
+
+ if (initPluginFn)
+ {
+ rdpSettings* settings = pEntryPoints->GetRdpSettings(pEntryPoints);
+ rdpContext* context = pEntryPoints->GetRdpContext(pEntryPoints);
+
+ error = initPluginFn(plugin, context, settings);
+ if (error != CHANNEL_RC_OK)
+ goto error;
+ }
+
+ plugin->dynvc_name = _strdup(name);
+ if (!plugin->dynvc_name)
+ goto error;
+
+ error = pEntryPoints->RegisterPlugin(pEntryPoints, name, &plugin->iface);
+ if (error == CHANNEL_RC_OK)
+ return error;
+
+error:
+ generic_plugin_terminated(&plugin->iface);
+ return error;
+}
diff --git a/channels/client/tables.c.in b/channels/client/tables.c.in
new file mode 100644
index 0000000..a22621b
--- /dev/null
+++ b/channels/client/tables.c.in
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Static Entry Point Tables
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/dvc.h>
+#include <freerdp/channels/rdpdr.h>
+#include "tables.h"
+
+${CLIENT_STATIC_TYPEDEFS}
+${CLIENT_STATIC_ENTRY_IMPORTS}
+${CLIENT_STATIC_ENTRY_TABLES}
+${CLIENT_STATIC_ENTRY_TABLES_LIST}
+${CLIENT_STATIC_SUBSYSTEM_IMPORTS}
+${CLIENT_STATIC_SUBSYSTEM_TABLES}
+${CLIENT_STATIC_ADDIN_TABLE}
+
diff --git a/channels/client/tables.h b/channels/client/tables.h
new file mode 100644
index 0000000..e67beb5
--- /dev/null
+++ b/channels/client/tables.h
@@ -0,0 +1,54 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Static Entry Point Tables
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <winpr/platform.h>
+#include <freerdp/svc.h>
+
+/* The 'entry' function pointers have variable arguments. */
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_STRICT_PROTOTYPES
+
+typedef struct
+{
+ const char* name;
+ UINT (*entry)();
+} STATIC_ENTRY;
+
+typedef struct
+{
+ const char* name;
+ const STATIC_ENTRY* table;
+} STATIC_ENTRY_TABLE;
+
+typedef struct
+{
+ const char* name;
+ const char* type;
+ UINT (*entry)();
+} STATIC_SUBSYSTEM_ENTRY;
+
+typedef struct
+{
+ const char* name;
+ const char* type;
+ UINT (*entry)();
+ const STATIC_SUBSYSTEM_ENTRY* table;
+} STATIC_ADDIN_TABLE;
+
+WINPR_PRAGMA_DIAG_POP
diff --git a/channels/cliprdr/CMakeLists.txt b/channels/cliprdr/CMakeLists.txt
new file mode 100644
index 0000000..c5cfd72
--- /dev/null
+++ b/channels/cliprdr/CMakeLists.txt
@@ -0,0 +1,26 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("cliprdr")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/cliprdr/ChannelOptions.cmake b/channels/cliprdr/ChannelOptions.cmake
new file mode 100644
index 0000000..f175f3f
--- /dev/null
+++ b/channels/cliprdr/ChannelOptions.cmake
@@ -0,0 +1,13 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT ON)
+
+define_channel_options(NAME "cliprdr" TYPE "static"
+ DESCRIPTION "Clipboard Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPECLIP]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/cliprdr/client/CMakeLists.txt b/channels/cliprdr/client/CMakeLists.txt
new file mode 100644
index 0000000..57819f0
--- /dev/null
+++ b/channels/cliprdr/client/CMakeLists.txt
@@ -0,0 +1,32 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("cliprdr")
+
+set(${MODULE_PREFIX}_SRCS
+ cliprdr_format.c
+ cliprdr_format.h
+ cliprdr_main.c
+ cliprdr_main.h
+ ../cliprdr_common.h
+ ../cliprdr_common.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr freerdp
+)
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx")
diff --git a/channels/cliprdr/client/cliprdr_format.c b/channels/cliprdr/client/cliprdr_format.c
new file mode 100644
index 0000000..8b13af4
--- /dev/null
+++ b/channels/cliprdr/client/cliprdr_format.c
@@ -0,0 +1,243 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Clipboard Virtual Channel
+ *
+ * Copyright 2009-2011 Jay Sorg
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include <freerdp/types.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/settings.h>
+#include <freerdp/constants.h>
+#include <freerdp/client/cliprdr.h>
+
+#include "cliprdr_main.h"
+#include "cliprdr_format.h"
+#include "../cliprdr_common.h"
+
+CLIPRDR_FORMAT_LIST cliprdr_filter_format_list(const CLIPRDR_FORMAT_LIST* list, const UINT32 mask,
+ const UINT32 checkMask)
+{
+ const UINT32 maskData =
+ checkMask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_REMOTE_TO_LOCAL);
+ const UINT32 maskFiles =
+ checkMask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES);
+ WINPR_ASSERT(list);
+
+ CLIPRDR_FORMAT_LIST filtered = { 0 };
+ filtered.common.msgType = CB_FORMAT_LIST;
+ filtered.numFormats = list->numFormats;
+ filtered.formats = calloc(filtered.numFormats, sizeof(CLIPRDR_FORMAT));
+
+ size_t wpos = 0;
+ if ((mask & checkMask) == checkMask)
+ {
+ for (size_t x = 0; x < list->numFormats; x++)
+ {
+ const CLIPRDR_FORMAT* format = &list->formats[x];
+ CLIPRDR_FORMAT* cur = &filtered.formats[x];
+ cur->formatId = format->formatId;
+ if (format->formatName)
+ cur->formatName = _strdup(format->formatName);
+ wpos++;
+ }
+ }
+ else if ((mask & maskFiles) != 0)
+ {
+ for (size_t x = 0; x < list->numFormats; x++)
+ {
+ const CLIPRDR_FORMAT* format = &list->formats[x];
+ CLIPRDR_FORMAT* cur = &filtered.formats[wpos];
+
+ if (!format->formatName)
+ continue;
+ if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0 ||
+ strcmp(format->formatName, type_FileContents) == 0)
+ {
+ cur->formatId = format->formatId;
+ cur->formatName = _strdup(format->formatName);
+ wpos++;
+ }
+ }
+ }
+ else if ((mask & maskData) != 0)
+ {
+ for (size_t x = 0; x < list->numFormats; x++)
+ {
+ const CLIPRDR_FORMAT* format = &list->formats[x];
+ CLIPRDR_FORMAT* cur = &filtered.formats[wpos];
+
+ if (!format->formatName ||
+ (strcmp(format->formatName, type_FileGroupDescriptorW) != 0 &&
+ strcmp(format->formatName, type_FileContents) != 0))
+ {
+ cur->formatId = format->formatId;
+ if (format->formatName)
+ cur->formatName = _strdup(format->formatName);
+ wpos++;
+ }
+ }
+ }
+ filtered.numFormats = wpos;
+ return filtered;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT cliprdr_process_format_list(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
+ UINT16 msgFlags)
+{
+ CLIPRDR_FORMAT_LIST formatList = { 0 };
+ CLIPRDR_FORMAT_LIST filteredFormatList = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ formatList.common.msgType = CB_FORMAT_LIST;
+ formatList.common.msgFlags = msgFlags;
+ formatList.common.dataLen = dataLen;
+
+ if ((error = cliprdr_read_format_list(s, &formatList, cliprdr->useLongFormatNames)))
+ goto error_out;
+
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
+ filteredFormatList = cliprdr_filter_format_list(
+ &formatList, mask, CLIPRDR_FLAG_REMOTE_TO_LOCAL | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES);
+ if (filteredFormatList.numFormats == 0)
+ goto error_out;
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatList: numFormats: %" PRIu32 "",
+ filteredFormatList.numFormats);
+
+ if (context->ServerFormatList)
+ {
+ if ((error = context->ServerFormatList(context, &filteredFormatList)))
+ WLog_ERR(TAG, "ServerFormatList failed with error %" PRIu32 "", error);
+ }
+
+error_out:
+ cliprdr_free_format_list(&filteredFormatList);
+ cliprdr_free_format_list(&formatList);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT cliprdr_process_format_list_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
+ UINT16 msgFlags)
+{
+ CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatListResponse");
+
+ formatListResponse.common.msgType = CB_FORMAT_LIST_RESPONSE;
+ formatListResponse.common.msgFlags = msgFlags;
+ formatListResponse.common.dataLen = dataLen;
+
+ IFCALLRET(context->ServerFormatListResponse, error, context, &formatListResponse);
+ if (error)
+ WLog_ERR(TAG, "ServerFormatListResponse failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT cliprdr_process_format_data_request(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
+ UINT16 msgFlags)
+{
+ CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatDataRequest");
+
+ formatDataRequest.common.msgType = CB_FORMAT_DATA_REQUEST;
+ formatDataRequest.common.msgFlags = msgFlags;
+ formatDataRequest.common.dataLen = dataLen;
+
+ if ((error = cliprdr_read_format_data_request(s, &formatDataRequest)))
+ return error;
+
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
+ if ((mask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES)) == 0)
+ {
+ return cliprdr_send_error_response(cliprdr, CB_FORMAT_DATA_RESPONSE);
+ }
+
+ context->lastRequestedFormatId = formatDataRequest.requestedFormatId;
+ IFCALLRET(context->ServerFormatDataRequest, error, context, &formatDataRequest);
+ if (error)
+ WLog_ERR(TAG, "ServerFormatDataRequest failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT cliprdr_process_format_data_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
+ UINT16 msgFlags)
+{
+ CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatDataResponse");
+
+ formatDataResponse.common.msgType = CB_FORMAT_DATA_RESPONSE;
+ formatDataResponse.common.msgFlags = msgFlags;
+ formatDataResponse.common.dataLen = dataLen;
+
+ if ((error = cliprdr_read_format_data_response(s, &formatDataResponse)))
+ return error;
+
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
+ if ((mask & (CLIPRDR_FLAG_REMOTE_TO_LOCAL | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES)) == 0)
+ {
+ WLog_WARN(TAG,
+ "Received ServerFormatDataResponse but remote -> local clipboard is disabled");
+ return CHANNEL_RC_OK;
+ }
+
+ IFCALLRET(context->ServerFormatDataResponse, error, context, &formatDataResponse);
+ if (error)
+ WLog_ERR(TAG, "ServerFormatDataResponse failed with error %" PRIu32 "!", error);
+
+ return error;
+}
diff --git a/channels/cliprdr/client/cliprdr_format.h b/channels/cliprdr/client/cliprdr_format.h
new file mode 100644
index 0000000..a3ba1ed
--- /dev/null
+++ b/channels/cliprdr/client/cliprdr_format.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Clipboard Virtual Channel
+ *
+ * Copyright 2009-2011 Jay Sorg
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_CLIPRDR_CLIENT_FORMAT_H
+#define FREERDP_CHANNEL_CLIPRDR_CLIENT_FORMAT_H
+
+UINT cliprdr_process_format_list(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
+ UINT16 msgFlags);
+UINT cliprdr_process_format_list_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
+ UINT16 msgFlags);
+UINT cliprdr_process_format_data_request(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
+ UINT16 msgFlags);
+UINT cliprdr_process_format_data_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
+ UINT16 msgFlags);
+CLIPRDR_FORMAT_LIST cliprdr_filter_format_list(const CLIPRDR_FORMAT_LIST* list, const UINT32 mask,
+ const UINT32 checkMask);
+
+#endif /* FREERDP_CHANNEL_CLIPRDR_CLIENT_FORMAT_H */
diff --git a/channels/cliprdr/client/cliprdr_main.c b/channels/cliprdr/client/cliprdr_main.c
new file mode 100644
index 0000000..60ae27d
--- /dev/null
+++ b/channels/cliprdr/client/cliprdr_main.c
@@ -0,0 +1,1192 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Clipboard Virtual Channel
+ *
+ * Copyright 2009-2011 Jay Sorg
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/wtypes.h>
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include <freerdp/types.h>
+#include <freerdp/constants.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/client/cliprdr.h>
+
+#include "../../../channels/client/addin.h"
+
+#include "cliprdr_main.h"
+#include "cliprdr_format.h"
+#include "../cliprdr_common.h"
+
+const char* type_FileGroupDescriptorW = "FileGroupDescriptorW";
+const char* type_FileContents = "FileContents";
+
+static const char* CB_MSG_TYPE_STRINGS(UINT32 type)
+{
+ switch (type)
+ {
+ case CB_MONITOR_READY:
+ return "CB_MONITOR_READY";
+ case CB_FORMAT_LIST:
+ return "CB_FORMAT_LIST";
+ case CB_FORMAT_LIST_RESPONSE:
+ return "CB_FORMAT_LIST_RESPONSE";
+ case CB_FORMAT_DATA_REQUEST:
+ return "CB_FORMAT_DATA_REQUEST";
+ case CB_FORMAT_DATA_RESPONSE:
+ return "CB_FORMAT_DATA_RESPONSE";
+ case CB_TEMP_DIRECTORY:
+ return "CB_TEMP_DIRECTORY";
+ case CB_CLIP_CAPS:
+ return "CB_CLIP_CAPS";
+ case CB_FILECONTENTS_REQUEST:
+ return "CB_FILECONTENTS_REQUEST";
+ case CB_FILECONTENTS_RESPONSE:
+ return "CB_FILECONTENTS_RESPONSE";
+ case CB_LOCK_CLIPDATA:
+ return "CB_LOCK_CLIPDATA";
+ case CB_UNLOCK_CLIPDATA:
+ return "CB_UNLOCK_CLIPDATA";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+CliprdrClientContext* cliprdr_get_client_interface(cliprdrPlugin* cliprdr)
+{
+ CliprdrClientContext* pInterface = NULL;
+
+ if (!cliprdr)
+ return NULL;
+
+ pInterface = (CliprdrClientContext*)cliprdr->channelEntryPoints.pInterface;
+ return pInterface;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_packet_send(cliprdrPlugin* cliprdr, wStream* s)
+{
+ size_t pos = 0;
+ UINT32 dataLen = 0;
+ UINT status = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(s);
+
+ pos = Stream_GetPosition(s);
+ dataLen = pos - 8;
+ Stream_SetPosition(s, 4);
+ Stream_Write_UINT32(s, dataLen);
+ Stream_SetPosition(s, pos);
+
+ WLog_DBG(TAG, "Cliprdr Sending (%" PRIu32 " bytes)", dataLen + 8);
+
+ if (!cliprdr)
+ {
+ status = CHANNEL_RC_BAD_INIT_HANDLE;
+ }
+ else
+ {
+ WINPR_ASSERT(cliprdr->channelEntryPoints.pVirtualChannelWriteEx);
+ status = cliprdr->channelEntryPoints.pVirtualChannelWriteEx(
+ cliprdr->InitHandle, cliprdr->OpenHandle, Stream_Buffer(s),
+ (UINT32)Stream_GetPosition(s), s);
+ }
+
+ if (status != CHANNEL_RC_OK)
+ {
+ Stream_Free(s, TRUE);
+ WLog_ERR(TAG, "VirtualChannelWrite failed with %s [%08" PRIX32 "]",
+ WTSErrorToString(status), status);
+ }
+
+ return status;
+}
+
+UINT cliprdr_send_error_response(cliprdrPlugin* cliprdr, UINT16 type)
+{
+ wStream* s = cliprdr_packet_new(type, CB_RESPONSE_FAIL, 0);
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_OUTOFMEMORY;
+ }
+
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+static void cliprdr_print_general_capability_flags(UINT32 flags)
+{
+ WLog_DBG(TAG, "generalFlags (0x%08" PRIX32 ") {", flags);
+
+ if (flags & CB_USE_LONG_FORMAT_NAMES)
+ WLog_DBG(TAG, "\tCB_USE_LONG_FORMAT_NAMES");
+
+ if (flags & CB_STREAM_FILECLIP_ENABLED)
+ WLog_DBG(TAG, "\tCB_STREAM_FILECLIP_ENABLED");
+
+ if (flags & CB_FILECLIP_NO_FILE_PATHS)
+ WLog_DBG(TAG, "\tCB_FILECLIP_NO_FILE_PATHS");
+
+ if (flags & CB_CAN_LOCK_CLIPDATA)
+ WLog_DBG(TAG, "\tCB_CAN_LOCK_CLIPDATA");
+
+ if (flags & CB_HUGE_FILE_SUPPORT_ENABLED)
+ WLog_DBG(TAG, "\tCB_HUGE_FILE_SUPPORT_ENABLED");
+
+ WLog_DBG(TAG, "}");
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_process_general_capability(cliprdrPlugin* cliprdr, wStream* s)
+{
+ UINT32 version = 0;
+ UINT32 generalFlags = 0;
+ CLIPRDR_CAPABILITIES capabilities = { 0 };
+ CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(s);
+
+ if (!context)
+ {
+ WLog_ERR(TAG, "cliprdr_get_client_interface failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, version); /* version (4 bytes) */
+ Stream_Read_UINT32(s, generalFlags); /* generalFlags (4 bytes) */
+ WLog_DBG(TAG, "Version: %" PRIu32 "", version);
+
+ cliprdr_print_general_capability_flags(generalFlags);
+
+ cliprdr->useLongFormatNames = (generalFlags & CB_USE_LONG_FORMAT_NAMES) ? TRUE : FALSE;
+ cliprdr->streamFileClipEnabled = (generalFlags & CB_STREAM_FILECLIP_ENABLED) ? TRUE : FALSE;
+ cliprdr->fileClipNoFilePaths = (generalFlags & CB_FILECLIP_NO_FILE_PATHS) ? TRUE : FALSE;
+ cliprdr->canLockClipData = (generalFlags & CB_CAN_LOCK_CLIPDATA) ? TRUE : FALSE;
+ cliprdr->hasHugeFileSupport = (generalFlags & CB_HUGE_FILE_SUPPORT_ENABLED) ? TRUE : FALSE;
+ cliprdr->capabilitiesReceived = TRUE;
+
+ capabilities.common.msgType = CB_CLIP_CAPS;
+ capabilities.cCapabilitiesSets = 1;
+ capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)&(generalCapabilitySet);
+ generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL;
+ generalCapabilitySet.capabilitySetLength = 12;
+ generalCapabilitySet.version = version;
+ generalCapabilitySet.generalFlags = generalFlags;
+ IFCALLRET(context->ServerCapabilities, error, context, &capabilities);
+
+ if (error)
+ WLog_ERR(TAG, "ServerCapabilities failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_process_clip_caps(cliprdrPlugin* cliprdr, wStream* s, UINT32 length,
+ UINT16 flags)
+{
+ UINT16 lengthCapability = 0;
+ UINT16 cCapabilitiesSets = 0;
+ UINT16 capabilitySetType = 0;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, cCapabilitiesSets); /* cCapabilitiesSets (2 bytes) */
+ Stream_Seek_UINT16(s); /* pad1 (2 bytes) */
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerCapabilities");
+
+ for (UINT16 index = 0; index < cCapabilitiesSets; index++)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, capabilitySetType); /* capabilitySetType (2 bytes) */
+ Stream_Read_UINT16(s, lengthCapability); /* lengthCapability (2 bytes) */
+
+ if ((lengthCapability < 4) ||
+ (!Stream_CheckAndLogRequiredLength(TAG, s, lengthCapability - 4U)))
+ return ERROR_INVALID_DATA;
+
+ switch (capabilitySetType)
+ {
+ case CB_CAPSTYPE_GENERAL:
+ if ((error = cliprdr_process_general_capability(cliprdr, s)))
+ {
+ WLog_ERR(TAG,
+ "cliprdr_process_general_capability failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "unknown cliprdr capability set: %" PRIu16 "", capabilitySetType);
+ return CHANNEL_RC_BAD_PROC;
+ }
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_process_monitor_ready(cliprdrPlugin* cliprdr, wStream* s, UINT32 length,
+ UINT16 flags)
+{
+ CLIPRDR_MONITOR_READY monitorReady = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(s);
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "MonitorReady");
+
+ if (!cliprdr->capabilitiesReceived)
+ {
+ /**
+ * The clipboard capabilities pdu from server to client is optional,
+ * but a server using it must send it before sending the monitor ready pdu.
+ * When the server capabilities pdu is not used, default capabilities
+ * corresponding to a generalFlags field set to zero are assumed.
+ */
+ cliprdr->useLongFormatNames = FALSE;
+ cliprdr->streamFileClipEnabled = FALSE;
+ cliprdr->fileClipNoFilePaths = TRUE;
+ cliprdr->canLockClipData = FALSE;
+ }
+
+ monitorReady.common.msgType = CB_MONITOR_READY;
+ monitorReady.common.msgFlags = flags;
+ monitorReady.common.dataLen = length;
+ IFCALLRET(context->MonitorReady, error, context, &monitorReady);
+
+ if (error)
+ WLog_ERR(TAG, "MonitorReady failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_process_filecontents_request(cliprdrPlugin* cliprdr, wStream* s, UINT32 length,
+ UINT16 flags)
+{
+ CLIPRDR_FILE_CONTENTS_REQUEST request = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(s);
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "FileContentsRequest");
+
+ request.common.msgType = CB_FILECONTENTS_REQUEST;
+ request.common.msgFlags = flags;
+ request.common.dataLen = length;
+
+ if ((error = cliprdr_read_file_contents_request(s, &request)))
+ return error;
+
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
+ if ((mask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES)) == 0)
+ {
+ WLog_WARN(TAG, "local -> remote file copy disabled, ignoring request");
+ return cliprdr_send_error_response(cliprdr, CB_FILECONTENTS_RESPONSE);
+ }
+ IFCALLRET(context->ServerFileContentsRequest, error, context, &request);
+
+ if (error)
+ WLog_ERR(TAG, "ServerFileContentsRequest failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_process_filecontents_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 length,
+ UINT16 flags)
+{
+ CLIPRDR_FILE_CONTENTS_RESPONSE response = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(s);
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "FileContentsResponse");
+
+ response.common.msgType = CB_FILECONTENTS_RESPONSE;
+ response.common.msgFlags = flags;
+ response.common.dataLen = length;
+
+ if ((error = cliprdr_read_file_contents_response(s, &response)))
+ return error;
+
+ IFCALLRET(context->ServerFileContentsResponse, error, context, &response);
+
+ if (error)
+ WLog_ERR(TAG, "ServerFileContentsResponse failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_process_lock_clipdata(cliprdrPlugin* cliprdr, wStream* s, UINT32 length,
+ UINT16 flags)
+{
+ CLIPRDR_LOCK_CLIPBOARD_DATA lockClipboardData = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(s);
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "LockClipData");
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ lockClipboardData.common.msgType = CB_LOCK_CLIPDATA;
+ lockClipboardData.common.msgFlags = flags;
+ lockClipboardData.common.dataLen = length;
+ Stream_Read_UINT32(s, lockClipboardData.clipDataId); /* clipDataId (4 bytes) */
+ IFCALLRET(context->ServerLockClipboardData, error, context, &lockClipboardData);
+
+ if (error)
+ WLog_ERR(TAG, "ServerLockClipboardData failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_process_unlock_clipdata(cliprdrPlugin* cliprdr, wStream* s, UINT32 length,
+ UINT16 flags)
+{
+ CLIPRDR_UNLOCK_CLIPBOARD_DATA unlockClipboardData = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(s);
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "UnlockClipData");
+
+ if ((error = cliprdr_read_unlock_clipdata(s, &unlockClipboardData)))
+ return error;
+
+ unlockClipboardData.common.msgType = CB_UNLOCK_CLIPDATA;
+ unlockClipboardData.common.msgFlags = flags;
+ unlockClipboardData.common.dataLen = length;
+
+ IFCALLRET(context->ServerUnlockClipboardData, error, context, &unlockClipboardData);
+
+ if (error)
+ WLog_ERR(TAG, "ServerUnlockClipboardData failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_order_recv(LPVOID userdata, wStream* s)
+{
+ cliprdrPlugin* cliprdr = userdata;
+ UINT16 msgType = 0;
+ UINT16 msgFlags = 0;
+ UINT32 dataLen = 0;
+ UINT error = 0;
+
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, msgType); /* msgType (2 bytes) */
+ Stream_Read_UINT16(s, msgFlags); /* msgFlags (2 bytes) */
+ Stream_Read_UINT32(s, dataLen); /* dataLen (4 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, dataLen))
+ return ERROR_INVALID_DATA;
+
+ WLog_DBG(TAG, "msgType: %s (%" PRIu16 "), msgFlags: %" PRIu16 " dataLen: %" PRIu32 "",
+ CB_MSG_TYPE_STRINGS(msgType), msgType, msgFlags, dataLen);
+
+ switch (msgType)
+ {
+ case CB_CLIP_CAPS:
+ if ((error = cliprdr_process_clip_caps(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG, "cliprdr_process_clip_caps failed with error %" PRIu32 "!", error);
+
+ break;
+
+ case CB_MONITOR_READY:
+ if ((error = cliprdr_process_monitor_ready(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG, "cliprdr_process_monitor_ready failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_FORMAT_LIST:
+ if ((error = cliprdr_process_format_list(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG, "cliprdr_process_format_list failed with error %" PRIu32 "!", error);
+
+ break;
+
+ case CB_FORMAT_LIST_RESPONSE:
+ if ((error = cliprdr_process_format_list_response(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG, "cliprdr_process_format_list_response failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_FORMAT_DATA_REQUEST:
+ if ((error = cliprdr_process_format_data_request(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG, "cliprdr_process_format_data_request failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_FORMAT_DATA_RESPONSE:
+ if ((error = cliprdr_process_format_data_response(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG, "cliprdr_process_format_data_response failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_FILECONTENTS_REQUEST:
+ if ((error = cliprdr_process_filecontents_request(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG, "cliprdr_process_filecontents_request failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_FILECONTENTS_RESPONSE:
+ if ((error = cliprdr_process_filecontents_response(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG,
+ "cliprdr_process_filecontents_response failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_LOCK_CLIPDATA:
+ if ((error = cliprdr_process_lock_clipdata(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG, "cliprdr_process_lock_clipdata failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_UNLOCK_CLIPDATA:
+ if ((error = cliprdr_process_unlock_clipdata(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG, "cliprdr_process_unlock_clipdata failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ default:
+ error = CHANNEL_RC_BAD_PROC;
+ WLog_ERR(TAG, "unknown msgType %" PRIu16 "", msgType);
+ break;
+ }
+
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Callback Interface
+ */
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_client_capabilities(CliprdrClientContext* context,
+ const CLIPRDR_CAPABILITIES* capabilities)
+{
+ wStream* s = NULL;
+ UINT32 flags = 0;
+ const CLIPRDR_GENERAL_CAPABILITY_SET* generalCapabilitySet = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ s = cliprdr_packet_new(CB_CLIP_CAPS, 0, 4 + CB_CAPSTYPE_GENERAL_LEN);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Write_UINT16(s, 1); /* cCapabilitiesSets */
+ Stream_Write_UINT16(s, 0); /* pad1 */
+ generalCapabilitySet = (const CLIPRDR_GENERAL_CAPABILITY_SET*)capabilities->capabilitySets;
+ Stream_Write_UINT16(s, generalCapabilitySet->capabilitySetType); /* capabilitySetType */
+ Stream_Write_UINT16(s, generalCapabilitySet->capabilitySetLength); /* lengthCapability */
+ Stream_Write_UINT32(s, generalCapabilitySet->version); /* version */
+ flags = generalCapabilitySet->generalFlags;
+
+ /* Client capabilities are sent in response to server capabilities.
+ * -> Do not request features the server does not support.
+ * -> Update clipboard context feature state to what was agreed upon.
+ */
+ if (!cliprdr->useLongFormatNames)
+ flags &= ~CB_USE_LONG_FORMAT_NAMES;
+ if (!cliprdr->streamFileClipEnabled)
+ flags &= ~CB_STREAM_FILECLIP_ENABLED;
+ if (!cliprdr->fileClipNoFilePaths)
+ flags &= ~CB_FILECLIP_NO_FILE_PATHS;
+ if (!cliprdr->canLockClipData)
+ flags &= ~CB_CAN_LOCK_CLIPDATA;
+ if (!cliprdr->hasHugeFileSupport)
+ flags &= ~CB_HUGE_FILE_SUPPORT_ENABLED;
+
+ cliprdr->useLongFormatNames = (flags & CB_USE_LONG_FORMAT_NAMES) ? TRUE : FALSE;
+ cliprdr->streamFileClipEnabled = (flags & CB_STREAM_FILECLIP_ENABLED) ? TRUE : FALSE;
+ cliprdr->fileClipNoFilePaths = (flags & CB_FILECLIP_NO_FILE_PATHS) ? TRUE : FALSE;
+ cliprdr->canLockClipData = (flags & CB_CAN_LOCK_CLIPDATA) ? TRUE : FALSE;
+ cliprdr->hasHugeFileSupport = (flags & CB_HUGE_FILE_SUPPORT_ENABLED) ? TRUE : FALSE;
+
+ Stream_Write_UINT32(s, flags); /* generalFlags */
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientCapabilities");
+
+ cliprdr->initialFormatListSent = FALSE;
+
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_temp_directory(CliprdrClientContext* context,
+ const CLIPRDR_TEMP_DIRECTORY* tempDirectory)
+{
+ wStream* s = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(tempDirectory);
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ const size_t tmpDirCharLen = sizeof(tempDirectory->szTempDir) / sizeof(WCHAR);
+ s = cliprdr_packet_new(CB_TEMP_DIRECTORY, 0, tmpDirCharLen * sizeof(WCHAR));
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (Stream_Write_UTF16_String_From_UTF8(s, tmpDirCharLen - 1, tempDirectory->szTempDir,
+ ARRAYSIZE(tempDirectory->szTempDir), TRUE) < 0)
+ return ERROR_INTERNAL_ERROR;
+ /* Path must be 260 UTF16 characters with '\0' termination.
+ * ensure this here */
+ Stream_Write_UINT16(s, 0);
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "TempDirectory: %s", tempDirectory->szTempDir);
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_client_format_list(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_LIST* formatList)
+{
+ wStream* s = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatList);
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
+ CLIPRDR_FORMAT_LIST filterList = cliprdr_filter_format_list(
+ formatList, mask, CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES);
+
+ /* Allow initial format list from monitor ready, but ignore later attempts */
+ if ((filterList.numFormats == 0) && cliprdr->initialFormatListSent)
+ {
+ cliprdr_free_format_list(&filterList);
+ return CHANNEL_RC_OK;
+ }
+ cliprdr->initialFormatListSent = TRUE;
+
+ s = cliprdr_packet_format_list_new(&filterList, cliprdr->useLongFormatNames);
+ cliprdr_free_format_list(&filterList);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_format_list_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFormatList: numFormats: %" PRIu32 "",
+ formatList->numFormats);
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_client_format_list_response(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse)
+{
+ wStream* s = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatListResponse);
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ s = cliprdr_packet_new(CB_FORMAT_LIST_RESPONSE, formatListResponse->common.msgFlags, 0);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFormatListResponse");
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_client_lock_clipboard_data(CliprdrClientContext* context,
+ const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData)
+{
+ wStream* s = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(lockClipboardData);
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ s = cliprdr_packet_lock_clipdata_new(lockClipboardData);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_lock_clipdata_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientLockClipboardData: clipDataId: 0x%08" PRIX32 "",
+ lockClipboardData->clipDataId);
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_client_unlock_clipboard_data(CliprdrClientContext* context,
+ const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
+{
+ wStream* s = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(unlockClipboardData);
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ s = cliprdr_packet_unlock_clipdata_new(unlockClipboardData);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_unlock_clipdata_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientUnlockClipboardData: clipDataId: 0x%08" PRIX32 "",
+ unlockClipboardData->clipDataId);
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_client_format_data_request(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest)
+{
+ wStream* s = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatDataRequest);
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
+ if ((mask & (CLIPRDR_FLAG_REMOTE_TO_LOCAL | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES)) == 0)
+ {
+ WLog_WARN(TAG, "remote -> local copy disabled, ignoring request");
+ return CHANNEL_RC_OK;
+ }
+ s = cliprdr_packet_new(CB_FORMAT_DATA_REQUEST, 0, 4);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Write_UINT32(s, formatDataRequest->requestedFormatId); /* requestedFormatId (4 bytes) */
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFormatDataRequest");
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_client_format_data_response(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse)
+{
+ wStream* s = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatDataResponse);
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
+ WINPR_ASSERT((mask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES)) != 0);
+
+ s = cliprdr_packet_new(CB_FORMAT_DATA_RESPONSE, formatDataResponse->common.msgFlags,
+ formatDataResponse->common.dataLen);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Write(s, formatDataResponse->requestedFormatData, formatDataResponse->common.dataLen);
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFormatDataResponse");
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_client_file_contents_request(CliprdrClientContext* context,
+ const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
+{
+ wStream* s = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(fileContentsRequest);
+
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
+ if ((mask & CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES) == 0)
+ {
+ WLog_WARN(TAG, "remote -> local file copy disabled, ignoring request");
+ return CHANNEL_RC_OK;
+ }
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ if (!cliprdr)
+ return ERROR_INTERNAL_ERROR;
+
+ if (!cliprdr->hasHugeFileSupport)
+ {
+ if (((UINT64)fileContentsRequest->cbRequested + fileContentsRequest->nPositionLow) >
+ UINT32_MAX)
+ return ERROR_INVALID_PARAMETER;
+ if (fileContentsRequest->nPositionHigh != 0)
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ s = cliprdr_packet_file_contents_request_new(fileContentsRequest);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_file_contents_request_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFileContentsRequest: streamId: 0x%08" PRIX32 "",
+ fileContentsRequest->streamId);
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_client_file_contents_response(CliprdrClientContext* context,
+ const CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse)
+{
+ wStream* s = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(fileContentsResponse);
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
+ if ((mask & CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES) == 0)
+ return cliprdr_send_error_response(cliprdr, CB_FILECONTENTS_RESPONSE);
+
+ s = cliprdr_packet_file_contents_response_new(fileContentsResponse);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_file_contents_response_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFileContentsResponse: streamId: 0x%08" PRIX32 "",
+ fileContentsResponse->streamId);
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+static VOID VCAPITYPE cliprdr_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle,
+ UINT event, LPVOID pData,
+ UINT32 dataLength, UINT32 totalLength,
+ UINT32 dataFlags)
+{
+ UINT error = CHANNEL_RC_OK;
+ cliprdrPlugin* cliprdr = (cliprdrPlugin*)lpUserParam;
+
+ switch (event)
+ {
+ case CHANNEL_EVENT_DATA_RECEIVED:
+ if (!cliprdr || (cliprdr->OpenHandle != openHandle))
+ {
+ WLog_ERR(TAG, "error no match");
+ return;
+ }
+ if ((error = channel_client_post_message(cliprdr->MsgsHandle, pData, dataLength,
+ totalLength, dataFlags)))
+ WLog_ERR(TAG, "failed with error %" PRIu32 "", error);
+
+ break;
+
+ case CHANNEL_EVENT_WRITE_CANCELLED:
+ case CHANNEL_EVENT_WRITE_COMPLETE:
+ {
+ wStream* s = (wStream*)pData;
+ Stream_Free(s, TRUE);
+ }
+ break;
+
+ case CHANNEL_EVENT_USER:
+ break;
+ }
+
+ if (error && cliprdr && cliprdr->context->rdpcontext)
+ setChannelError(cliprdr->context->rdpcontext, error,
+ "cliprdr_virtual_channel_open_event_ex reported an error");
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_virtual_channel_event_connected(cliprdrPlugin* cliprdr, LPVOID pData,
+ UINT32 dataLength)
+{
+ DWORD status = 0;
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(cliprdr->context);
+
+ WINPR_ASSERT(cliprdr->channelEntryPoints.pVirtualChannelOpenEx);
+ status = cliprdr->channelEntryPoints.pVirtualChannelOpenEx(
+ cliprdr->InitHandle, &cliprdr->OpenHandle, cliprdr->channelDef.name,
+ cliprdr_virtual_channel_open_event_ex);
+ if (status != CHANNEL_RC_OK)
+ return status;
+
+ cliprdr->MsgsHandle = channel_client_create_handler(
+ cliprdr->context->rdpcontext, cliprdr, cliprdr_order_recv, CLIPRDR_SVC_CHANNEL_NAME);
+ if (!cliprdr->MsgsHandle)
+ return ERROR_INTERNAL_ERROR;
+
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_virtual_channel_event_disconnected(cliprdrPlugin* cliprdr)
+{
+ UINT rc = 0;
+
+ WINPR_ASSERT(cliprdr);
+
+ channel_client_quit_handler(cliprdr->MsgsHandle);
+ cliprdr->MsgsHandle = NULL;
+
+ if (cliprdr->OpenHandle == 0)
+ return CHANNEL_RC_OK;
+
+ WINPR_ASSERT(cliprdr->channelEntryPoints.pVirtualChannelCloseEx);
+ rc = cliprdr->channelEntryPoints.pVirtualChannelCloseEx(cliprdr->InitHandle,
+ cliprdr->OpenHandle);
+
+ if (CHANNEL_RC_OK != rc)
+ {
+ WLog_ERR(TAG, "pVirtualChannelClose failed with %s [%08" PRIX32 "]", WTSErrorToString(rc),
+ rc);
+ return rc;
+ }
+
+ cliprdr->OpenHandle = 0;
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_virtual_channel_event_terminated(cliprdrPlugin* cliprdr)
+{
+ WINPR_ASSERT(cliprdr);
+
+ cliprdr->InitHandle = 0;
+ free(cliprdr->context);
+ free(cliprdr);
+ return CHANNEL_RC_OK;
+}
+
+static VOID VCAPITYPE cliprdr_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle,
+ UINT event, LPVOID pData,
+ UINT dataLength)
+{
+ UINT error = CHANNEL_RC_OK;
+ cliprdrPlugin* cliprdr = (cliprdrPlugin*)lpUserParam;
+
+ if (!cliprdr || (cliprdr->InitHandle != pInitHandle))
+ {
+ WLog_ERR(TAG, "error no match");
+ return;
+ }
+
+ switch (event)
+ {
+ case CHANNEL_EVENT_CONNECTED:
+ if ((error = cliprdr_virtual_channel_event_connected(cliprdr, pData, dataLength)))
+ WLog_ERR(TAG,
+ "cliprdr_virtual_channel_event_connected failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_DISCONNECTED:
+ if ((error = cliprdr_virtual_channel_event_disconnected(cliprdr)))
+ WLog_ERR(TAG,
+ "cliprdr_virtual_channel_event_disconnected failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_TERMINATED:
+ if ((error = cliprdr_virtual_channel_event_terminated(cliprdr)))
+ WLog_ERR(TAG,
+ "cliprdr_virtual_channel_event_terminated failed with error %" PRIu32 "!",
+ error);
+
+ break;
+ }
+
+ if (error && cliprdr->context->rdpcontext)
+ setChannelError(cliprdr->context->rdpcontext, error,
+ "cliprdr_virtual_channel_init_event reported an error");
+}
+
+/* cliprdr is always built-in */
+#define VirtualChannelEntryEx cliprdr_VirtualChannelEntryEx
+
+FREERDP_ENTRY_POINT(BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS pEntryPoints,
+ PVOID pInitHandle))
+{
+ UINT rc = 0;
+ cliprdrPlugin* cliprdr = NULL;
+ CliprdrClientContext* context = NULL;
+ CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx = NULL;
+ cliprdr = (cliprdrPlugin*)calloc(1, sizeof(cliprdrPlugin));
+
+ if (!cliprdr)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return FALSE;
+ }
+
+ cliprdr->channelDef.options = CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP |
+ CHANNEL_OPTION_COMPRESS_RDP | CHANNEL_OPTION_SHOW_PROTOCOL;
+ sprintf_s(cliprdr->channelDef.name, ARRAYSIZE(cliprdr->channelDef.name),
+ CLIPRDR_SVC_CHANNEL_NAME);
+ pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints;
+ WINPR_ASSERT(pEntryPointsEx);
+
+ if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) &&
+ (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER))
+ {
+ context = (CliprdrClientContext*)calloc(1, sizeof(CliprdrClientContext));
+
+ if (!context)
+ {
+ free(cliprdr);
+ WLog_ERR(TAG, "calloc failed!");
+ return FALSE;
+ }
+
+ context->handle = (void*)cliprdr;
+ context->custom = NULL;
+ context->ClientCapabilities = cliprdr_client_capabilities;
+ context->TempDirectory = cliprdr_temp_directory;
+ context->ClientFormatList = cliprdr_client_format_list;
+ context->ClientFormatListResponse = cliprdr_client_format_list_response;
+ context->ClientLockClipboardData = cliprdr_client_lock_clipboard_data;
+ context->ClientUnlockClipboardData = cliprdr_client_unlock_clipboard_data;
+ context->ClientFormatDataRequest = cliprdr_client_format_data_request;
+ context->ClientFormatDataResponse = cliprdr_client_format_data_response;
+ context->ClientFileContentsRequest = cliprdr_client_file_contents_request;
+ context->ClientFileContentsResponse = cliprdr_client_file_contents_response;
+ cliprdr->context = context;
+ context->rdpcontext = pEntryPointsEx->context;
+ }
+
+ cliprdr->log = WLog_Get(CHANNELS_TAG("channels.cliprdr.client"));
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "VirtualChannelEntryEx");
+ CopyMemory(&(cliprdr->channelEntryPoints), pEntryPoints,
+ sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX));
+ cliprdr->InitHandle = pInitHandle;
+ rc = cliprdr->channelEntryPoints.pVirtualChannelInitEx(
+ cliprdr, context, pInitHandle, &cliprdr->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000,
+ cliprdr_virtual_channel_init_event_ex);
+
+ if (CHANNEL_RC_OK != rc)
+ {
+ WLog_ERR(TAG, "pVirtualChannelInit failed with %s [%08" PRIX32 "]", WTSErrorToString(rc),
+ rc);
+ free(cliprdr->context);
+ free(cliprdr);
+ return FALSE;
+ }
+
+ cliprdr->channelEntryPoints.pInterface = context;
+ return TRUE;
+}
diff --git a/channels/cliprdr/client/cliprdr_main.h b/channels/cliprdr/client/cliprdr_main.h
new file mode 100644
index 0000000..9e899f5
--- /dev/null
+++ b/channels/cliprdr/client/cliprdr_main.h
@@ -0,0 +1,61 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Clipboard Virtual Channel
+ *
+ * Copyright 2009-2011 Jay Sorg
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H
+
+#include <winpr/stream.h>
+
+#include <freerdp/svc.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/client/cliprdr.h>
+
+#define TAG CHANNELS_TAG("cliprdr.client")
+
+typedef struct
+{
+ CHANNEL_DEF channelDef;
+ CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints;
+
+ CliprdrClientContext* context;
+
+ wLog* log;
+ void* InitHandle;
+ DWORD OpenHandle;
+ void* MsgsHandle;
+
+ BOOL capabilitiesReceived;
+ BOOL useLongFormatNames;
+ BOOL streamFileClipEnabled;
+ BOOL fileClipNoFilePaths;
+ BOOL canLockClipData;
+ BOOL hasHugeFileSupport;
+ BOOL initialFormatListSent;
+} cliprdrPlugin;
+
+CliprdrClientContext* cliprdr_get_client_interface(cliprdrPlugin* cliprdr);
+UINT cliprdr_send_error_response(cliprdrPlugin* cliprdr, UINT16 type);
+
+extern const char* type_FileGroupDescriptorW;
+extern const char* type_FileContents;
+
+#endif /* FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H */
diff --git a/channels/cliprdr/cliprdr_common.c b/channels/cliprdr/cliprdr_common.c
new file mode 100644
index 0000000..d346cb1
--- /dev/null
+++ b/channels/cliprdr/cliprdr_common.c
@@ -0,0 +1,566 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Cliprdr common
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("cliprdr.common")
+
+#include "cliprdr_common.h"
+
+static BOOL cliprdr_validate_file_contents_request(const CLIPRDR_FILE_CONTENTS_REQUEST* request)
+{
+ /*
+ * [MS-RDPECLIP] 2.2.5.3 File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST).
+ *
+ * A request for the size of the file identified by the lindex field. The size MUST be
+ * returned as a 64-bit, unsigned integer. The cbRequested field MUST be set to
+ * 0x00000008 and both the nPositionLow and nPositionHigh fields MUST be
+ * set to 0x00000000.
+ */
+
+ if (request->dwFlags & FILECONTENTS_SIZE)
+ {
+ if (request->cbRequested != sizeof(UINT64))
+ {
+ WLog_ERR(TAG, "cbRequested must be %" PRIu32 ", got %" PRIu32 "", sizeof(UINT64),
+ request->cbRequested);
+ return FALSE;
+ }
+
+ if (request->nPositionHigh != 0 || request->nPositionLow != 0)
+ {
+ WLog_ERR(TAG, "nPositionHigh and nPositionLow must be set to 0");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+wStream* cliprdr_packet_new(UINT16 msgType, UINT16 msgFlags, UINT32 dataLen)
+{
+ wStream* s = NULL;
+ s = Stream_New(NULL, dataLen + 8);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return NULL;
+ }
+
+ Stream_Write_UINT16(s, msgType);
+ Stream_Write_UINT16(s, msgFlags);
+ /* Write actual length after the entire packet has been constructed. */
+ Stream_Write_UINT32(s, 0);
+ return s;
+}
+
+static void cliprdr_write_file_contents_request(wStream* s,
+ const CLIPRDR_FILE_CONTENTS_REQUEST* request)
+{
+ Stream_Write_UINT32(s, request->streamId); /* streamId (4 bytes) */
+ Stream_Write_UINT32(s, request->listIndex); /* listIndex (4 bytes) */
+ Stream_Write_UINT32(s, request->dwFlags); /* dwFlags (4 bytes) */
+ Stream_Write_UINT32(s, request->nPositionLow); /* nPositionLow (4 bytes) */
+ Stream_Write_UINT32(s, request->nPositionHigh); /* nPositionHigh (4 bytes) */
+ Stream_Write_UINT32(s, request->cbRequested); /* cbRequested (4 bytes) */
+
+ if (request->haveClipDataId)
+ Stream_Write_UINT32(s, request->clipDataId); /* clipDataId (4 bytes) */
+}
+
+static INLINE void cliprdr_write_lock_unlock_clipdata(wStream* s, UINT32 clipDataId)
+{
+ Stream_Write_UINT32(s, clipDataId);
+}
+
+static void cliprdr_write_lock_clipdata(wStream* s,
+ const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData)
+{
+ cliprdr_write_lock_unlock_clipdata(s, lockClipboardData->clipDataId);
+}
+
+static void cliprdr_write_unlock_clipdata(wStream* s,
+ const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
+{
+ cliprdr_write_lock_unlock_clipdata(s, unlockClipboardData->clipDataId);
+}
+
+static void cliprdr_write_file_contents_response(wStream* s,
+ const CLIPRDR_FILE_CONTENTS_RESPONSE* response)
+{
+ Stream_Write_UINT32(s, response->streamId); /* streamId (4 bytes) */
+ Stream_Write(s, response->requestedData, response->cbRequested);
+}
+
+wStream* cliprdr_packet_lock_clipdata_new(const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData)
+{
+ wStream* s = NULL;
+
+ if (!lockClipboardData)
+ return NULL;
+
+ s = cliprdr_packet_new(CB_LOCK_CLIPDATA, 0, 4);
+
+ if (!s)
+ return NULL;
+
+ cliprdr_write_lock_clipdata(s, lockClipboardData);
+ return s;
+}
+
+wStream*
+cliprdr_packet_unlock_clipdata_new(const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
+{
+ wStream* s = NULL;
+
+ if (!unlockClipboardData)
+ return NULL;
+
+ s = cliprdr_packet_new(CB_UNLOCK_CLIPDATA, 0, 4);
+
+ if (!s)
+ return NULL;
+
+ cliprdr_write_unlock_clipdata(s, unlockClipboardData);
+ return s;
+}
+
+wStream* cliprdr_packet_file_contents_request_new(const CLIPRDR_FILE_CONTENTS_REQUEST* request)
+{
+ wStream* s = NULL;
+
+ if (!request)
+ return NULL;
+
+ s = cliprdr_packet_new(CB_FILECONTENTS_REQUEST, 0, 28);
+
+ if (!s)
+ return NULL;
+
+ cliprdr_write_file_contents_request(s, request);
+ return s;
+}
+
+wStream* cliprdr_packet_file_contents_response_new(const CLIPRDR_FILE_CONTENTS_RESPONSE* response)
+{
+ wStream* s = NULL;
+
+ if (!response)
+ return NULL;
+
+ s = cliprdr_packet_new(CB_FILECONTENTS_RESPONSE, response->common.msgFlags,
+ 4 + response->cbRequested);
+
+ if (!s)
+ return NULL;
+
+ cliprdr_write_file_contents_response(s, response);
+ return s;
+}
+
+wStream* cliprdr_packet_format_list_new(const CLIPRDR_FORMAT_LIST* formatList,
+ BOOL useLongFormatNames)
+{
+ wStream* s = NULL;
+ size_t formatNameSize = 0;
+ char* szFormatName = NULL;
+ WCHAR* wszFormatName = NULL;
+ BOOL asciiNames = FALSE;
+ CLIPRDR_FORMAT* format = NULL;
+ UINT32 length = 0;
+
+ if (formatList->common.msgType != CB_FORMAT_LIST)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, formatList->common.msgType);
+
+ if (!useLongFormatNames)
+ {
+ length = formatList->numFormats * 36;
+ s = cliprdr_packet_new(CB_FORMAT_LIST, 0, length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return NULL;
+ }
+
+ for (UINT32 index = 0; index < formatList->numFormats; index++)
+ {
+ size_t formatNameLength = 0;
+ format = (CLIPRDR_FORMAT*)&(formatList->formats[index]);
+ Stream_Write_UINT32(s, format->formatId); /* formatId (4 bytes) */
+ formatNameSize = 0;
+
+ szFormatName = format->formatName;
+
+ if (asciiNames)
+ {
+ if (szFormatName)
+ formatNameLength = strnlen(szFormatName, 32);
+
+ if (formatNameLength > 31)
+ formatNameLength = 31;
+
+ Stream_Write(s, szFormatName, formatNameLength);
+ Stream_Zero(s, 32 - formatNameLength);
+ }
+ else
+ {
+ wszFormatName = NULL;
+
+ if (szFormatName)
+ {
+ wszFormatName = ConvertUtf8ToWCharAlloc(szFormatName, &formatNameSize);
+
+ if (!wszFormatName)
+ {
+ Stream_Free(s, TRUE);
+ return NULL;
+ }
+ formatNameSize += 1; /* append terminating '\0' */
+ }
+
+ if (formatNameSize > 15)
+ formatNameSize = 15;
+
+ /* size in bytes instead of wchar */
+ formatNameSize *= sizeof(WCHAR);
+
+ if (wszFormatName)
+ Stream_Write(s, wszFormatName, (size_t)formatNameSize);
+
+ Stream_Zero(s, (size_t)(32 - formatNameSize));
+ free(wszFormatName);
+ }
+ }
+ }
+ else
+ {
+ length = 0;
+ for (UINT32 index = 0; index < formatList->numFormats; index++)
+ {
+ format = (CLIPRDR_FORMAT*)&(formatList->formats[index]);
+ length += 4;
+ formatNameSize = sizeof(WCHAR);
+
+ if (format->formatName)
+ {
+ SSIZE_T size = ConvertUtf8ToWChar(format->formatName, NULL, 0);
+ if (size < 0)
+ return NULL;
+ formatNameSize = (size_t)(size + 1) * sizeof(WCHAR);
+ }
+
+ length += (UINT32)formatNameSize;
+ }
+
+ s = cliprdr_packet_new(CB_FORMAT_LIST, 0, length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return NULL;
+ }
+
+ for (UINT32 index = 0; index < formatList->numFormats; index++)
+ {
+ format = (CLIPRDR_FORMAT*)&(formatList->formats[index]);
+ Stream_Write_UINT32(s, format->formatId); /* formatId (4 bytes) */
+
+ if (format->formatName)
+ {
+ const size_t cap = Stream_Capacity(s);
+ const size_t pos = Stream_GetPosition(s);
+ const size_t rem = cap - pos;
+ if ((cap < pos) || ((rem / 2) > INT_MAX))
+ {
+ Stream_Free(s, TRUE);
+ return NULL;
+ }
+
+ const size_t len = strnlen(format->formatName, rem / sizeof(WCHAR));
+ if (Stream_Write_UTF16_String_From_UTF8(s, len + 1, format->formatName, len, TRUE) <
+ 0)
+ {
+ Stream_Free(s, TRUE);
+ return NULL;
+ }
+ }
+ else
+ {
+ Stream_Write_UINT16(s, 0);
+ }
+ }
+ }
+
+ return s;
+}
+UINT cliprdr_read_unlock_clipdata(wStream* s, CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, unlockClipboardData->clipDataId); /* clipDataId (4 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+UINT cliprdr_read_format_data_request(wStream* s, CLIPRDR_FORMAT_DATA_REQUEST* request)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, request->requestedFormatId); /* requestedFormatId (4 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+UINT cliprdr_read_format_data_response(wStream* s, CLIPRDR_FORMAT_DATA_RESPONSE* response)
+{
+ response->requestedFormatData = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, response->common.dataLen))
+ return ERROR_INVALID_DATA;
+
+ if (response->common.dataLen)
+ response->requestedFormatData = Stream_ConstPointer(s);
+
+ return CHANNEL_RC_OK;
+}
+
+UINT cliprdr_read_file_contents_request(wStream* s, CLIPRDR_FILE_CONTENTS_REQUEST* request)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 24))
+ return ERROR_INVALID_DATA;
+
+ request->haveClipDataId = FALSE;
+ Stream_Read_UINT32(s, request->streamId); /* streamId (4 bytes) */
+ Stream_Read_UINT32(s, request->listIndex); /* listIndex (4 bytes) */
+ Stream_Read_UINT32(s, request->dwFlags); /* dwFlags (4 bytes) */
+ Stream_Read_UINT32(s, request->nPositionLow); /* nPositionLow (4 bytes) */
+ Stream_Read_UINT32(s, request->nPositionHigh); /* nPositionHigh (4 bytes) */
+ Stream_Read_UINT32(s, request->cbRequested); /* cbRequested (4 bytes) */
+
+ if (Stream_GetRemainingLength(s) >= 4)
+ {
+ Stream_Read_UINT32(s, request->clipDataId); /* clipDataId (4 bytes) */
+ request->haveClipDataId = TRUE;
+ }
+
+ if (!cliprdr_validate_file_contents_request(request))
+ return ERROR_BAD_ARGUMENTS;
+
+ return CHANNEL_RC_OK;
+}
+
+UINT cliprdr_read_file_contents_response(wStream* s, CLIPRDR_FILE_CONTENTS_RESPONSE* response)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, response->streamId); /* streamId (4 bytes) */
+ response->requestedData = Stream_ConstPointer(s); /* requestedFileContentsData */
+
+ WINPR_ASSERT(response->common.dataLen >= 4);
+ response->cbRequested = response->common.dataLen - 4;
+ return CHANNEL_RC_OK;
+}
+
+UINT cliprdr_read_format_list(wStream* s, CLIPRDR_FORMAT_LIST* formatList, BOOL useLongFormatNames)
+{
+ UINT32 index = 0;
+ int formatNameLength = 0;
+ const char* szFormatName = NULL;
+ const WCHAR* wszFormatName = NULL;
+ wStream sub1buffer = { 0 };
+ CLIPRDR_FORMAT* formats = NULL;
+ UINT error = ERROR_INTERNAL_ERROR;
+
+ const BOOL asciiNames = (formatList->common.msgFlags & CB_ASCII_NAMES) ? TRUE : FALSE;
+
+ index = 0;
+ /* empty format list */
+ formatList->formats = NULL;
+ formatList->numFormats = 0;
+
+ wStream* sub1 =
+ Stream_StaticConstInit(&sub1buffer, Stream_ConstPointer(s), formatList->common.dataLen);
+ if (!Stream_SafeSeek(s, formatList->common.dataLen))
+ return ERROR_INVALID_DATA;
+
+ if (!formatList->common.dataLen)
+ {
+ }
+ else if (!useLongFormatNames)
+ {
+ const size_t cap = Stream_Capacity(sub1);
+ formatList->numFormats = (cap / 36);
+
+ if ((formatList->numFormats * 36) != cap)
+ {
+ WLog_ERR(TAG, "Invalid short format list length: %" PRIuz "", cap);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (formatList->numFormats)
+ formats = (CLIPRDR_FORMAT*)calloc(formatList->numFormats, sizeof(CLIPRDR_FORMAT));
+
+ if (!formats)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ formatList->formats = formats;
+
+ while (Stream_GetRemainingLength(sub1) >= 4)
+ {
+ CLIPRDR_FORMAT* format = &formats[index];
+
+ Stream_Read_UINT32(sub1, format->formatId); /* formatId (4 bytes) */
+
+ /* According to MS-RDPECLIP 2.2.3.1.1.1 formatName is "a 32-byte block containing
+ * the *null-terminated* name assigned to the Clipboard Format: (32 ASCII 8 characters
+ * or 16 Unicode characters)"
+ * However, both Windows RDSH and mstsc violate this specs as seen in the following
+ * example of a transferred short format name string: [R.i.c.h. .T.e.x.t. .F.o.r.m.a.t.]
+ * These are 16 unicode charaters - *without* terminating null !
+ */
+
+ szFormatName = Stream_ConstPointer(sub1);
+ wszFormatName = Stream_ConstPointer(sub1);
+ if (!Stream_SafeSeek(sub1, 32))
+ goto error_out;
+
+ free(format->formatName);
+ format->formatName = NULL;
+
+ if (asciiNames)
+ {
+ if (szFormatName[0])
+ {
+ /* ensure null termination */
+ format->formatName = strndup(szFormatName, 31);
+ if (!format->formatName)
+ {
+ WLog_ERR(TAG, "malloc failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+ }
+ }
+ else
+ {
+ if (wszFormatName[0])
+ {
+ format->formatName = ConvertWCharNToUtf8Alloc(wszFormatName, 16, NULL);
+ if (!format->formatName)
+ goto error_out;
+ }
+ }
+
+ index++;
+ }
+ }
+ else
+ {
+ wStream sub2buffer = sub1buffer;
+ wStream* sub2 = &sub2buffer;
+
+ while (Stream_GetRemainingLength(sub1) > 0)
+ {
+ size_t rest = 0;
+ if (!Stream_SafeSeek(sub1, 4)) /* formatId (4 bytes) */
+ goto error_out;
+
+ wszFormatName = Stream_ConstPointer(sub1);
+ rest = Stream_GetRemainingLength(sub1);
+ formatNameLength = _wcsnlen(wszFormatName, rest / sizeof(WCHAR));
+
+ if (!Stream_SafeSeek(sub1, (formatNameLength + 1) * sizeof(WCHAR)))
+ goto error_out;
+ formatList->numFormats++;
+ }
+
+ if (formatList->numFormats)
+ formats = (CLIPRDR_FORMAT*)calloc(formatList->numFormats, sizeof(CLIPRDR_FORMAT));
+
+ if (!formats)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ formatList->formats = formats;
+
+ while (Stream_GetRemainingLength(sub2) >= 4)
+ {
+ size_t rest = 0;
+ CLIPRDR_FORMAT* format = &formats[index];
+
+ Stream_Read_UINT32(sub2, format->formatId); /* formatId (4 bytes) */
+
+ free(format->formatName);
+ format->formatName = NULL;
+
+ wszFormatName = Stream_ConstPointer(sub2);
+ rest = Stream_GetRemainingLength(sub2);
+ formatNameLength = _wcsnlen(wszFormatName, rest / sizeof(WCHAR));
+ if (!Stream_SafeSeek(sub2, (formatNameLength + 1) * sizeof(WCHAR)))
+ goto error_out;
+
+ if (formatNameLength)
+ {
+ format->formatName =
+ ConvertWCharNToUtf8Alloc(wszFormatName, formatNameLength, NULL);
+ if (!format->formatName)
+ goto error_out;
+ }
+
+ index++;
+ }
+ }
+
+ return CHANNEL_RC_OK;
+
+error_out:
+ cliprdr_free_format_list(formatList);
+ return error;
+}
+
+void cliprdr_free_format_list(CLIPRDR_FORMAT_LIST* formatList)
+{
+ if (formatList == NULL)
+ return;
+
+ if (formatList->formats)
+ {
+ for (UINT32 index = 0; index < formatList->numFormats; index++)
+ {
+ free(formatList->formats[index].formatName);
+ }
+
+ free(formatList->formats);
+ formatList->formats = NULL;
+ formatList->numFormats = 0;
+ }
+}
diff --git a/channels/cliprdr/cliprdr_common.h b/channels/cliprdr/cliprdr_common.h
new file mode 100644
index 0000000..b5d36b9
--- /dev/null
+++ b/channels/cliprdr/cliprdr_common.h
@@ -0,0 +1,61 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Cliprdr common
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPECLIP_COMMON_H
+#define FREERDP_CHANNEL_RDPECLIP_COMMON_H
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+
+#include <freerdp/channels/cliprdr.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL wStream* cliprdr_packet_new(UINT16 msgType, UINT16 msgFlags, UINT32 dataLen);
+FREERDP_LOCAL wStream*
+cliprdr_packet_lock_clipdata_new(const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData);
+FREERDP_LOCAL wStream*
+cliprdr_packet_unlock_clipdata_new(const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData);
+FREERDP_LOCAL wStream*
+cliprdr_packet_file_contents_request_new(const CLIPRDR_FILE_CONTENTS_REQUEST* request);
+FREERDP_LOCAL wStream*
+cliprdr_packet_file_contents_response_new(const CLIPRDR_FILE_CONTENTS_RESPONSE* response);
+FREERDP_LOCAL wStream* cliprdr_packet_format_list_new(const CLIPRDR_FORMAT_LIST* formatList,
+ BOOL useLongFormatNames);
+
+FREERDP_LOCAL UINT cliprdr_read_lock_clipdata(wStream* s,
+ CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData);
+FREERDP_LOCAL UINT cliprdr_read_unlock_clipdata(wStream* s,
+ CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData);
+FREERDP_LOCAL UINT cliprdr_read_format_data_request(wStream* s,
+ CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest);
+FREERDP_LOCAL UINT cliprdr_read_format_data_response(wStream* s,
+ CLIPRDR_FORMAT_DATA_RESPONSE* response);
+FREERDP_LOCAL UINT
+cliprdr_read_file_contents_request(wStream* s, CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest);
+FREERDP_LOCAL UINT cliprdr_read_file_contents_response(wStream* s,
+ CLIPRDR_FILE_CONTENTS_RESPONSE* response);
+FREERDP_LOCAL UINT cliprdr_read_format_list(wStream* s, CLIPRDR_FORMAT_LIST* formatList,
+ BOOL useLongFormatNames);
+
+FREERDP_LOCAL void cliprdr_free_format_list(CLIPRDR_FORMAT_LIST* formatList);
+
+#endif /* FREERDP_CHANNEL_RDPECLIP_COMMON_H */
diff --git a/channels/cliprdr/server/CMakeLists.txt b/channels/cliprdr/server/CMakeLists.txt
new file mode 100644
index 0000000..09c6a7e
--- /dev/null
+++ b/channels/cliprdr/server/CMakeLists.txt
@@ -0,0 +1,30 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_server("cliprdr")
+
+set(${MODULE_PREFIX}_SRCS
+ cliprdr_main.c
+ cliprdr_main.h
+ ../cliprdr_common.h
+ ../cliprdr_common.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry")
diff --git a/channels/cliprdr/server/cliprdr_main.c b/channels/cliprdr/server/cliprdr_main.c
new file mode 100644
index 0000000..9823f17
--- /dev/null
+++ b/channels/cliprdr/server/cliprdr_main.c
@@ -0,0 +1,1528 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Clipboard Virtual Channel Extension
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/log.h>
+#include "cliprdr_main.h"
+#include "../cliprdr_common.h"
+
+/**
+ * Initialization Sequence\n
+ * Client Server\n
+ * | |\n
+ * |<----------------------Server Clipboard Capabilities PDU-----------------|\n
+ * |<-----------------------------Monitor Ready PDU--------------------------|\n
+ * |-----------------------Client Clipboard Capabilities PDU---------------->|\n
+ * |---------------------------Temporary Directory PDU---------------------->|\n
+ * |-------------------------------Format List PDU-------------------------->|\n
+ * |<--------------------------Format List Response PDU----------------------|\n
+ *
+ */
+
+/**
+ * Data Transfer Sequences\n
+ * Shared Local\n
+ * Clipboard Owner Clipboard Owner\n
+ * | |\n
+ * |-------------------------------------------------------------------------|\n _
+ * |-------------------------------Format List PDU-------------------------->|\n |
+ * |<--------------------------Format List Response PDU----------------------|\n _| Copy
+ * Sequence
+ * |<---------------------Lock Clipboard Data PDU (Optional)-----------------|\n
+ * |-------------------------------------------------------------------------|\n
+ * |-------------------------------------------------------------------------|\n _
+ * |<--------------------------Format Data Request PDU-----------------------|\n | Paste
+ * Sequence Palette,
+ * |---------------------------Format Data Response PDU--------------------->|\n _| Metafile,
+ * File List Data
+ * |-------------------------------------------------------------------------|\n
+ * |-------------------------------------------------------------------------|\n _
+ * |<------------------------Format Contents Request PDU---------------------|\n | Paste
+ * Sequence
+ * |-------------------------Format Contents Response PDU------------------->|\n _| File
+ * Stream Data
+ * |<---------------------Lock Clipboard Data PDU (Optional)-----------------|\n
+ * |-------------------------------------------------------------------------|\n
+ *
+ */
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_packet_send(CliprdrServerPrivate* cliprdr, wStream* s)
+{
+ UINT rc = 0;
+ size_t pos = 0;
+ BOOL status = 0;
+ UINT32 dataLen = 0;
+ ULONG written = 0;
+
+ WINPR_ASSERT(cliprdr);
+
+ pos = Stream_GetPosition(s);
+ if ((pos < 8) || (pos > UINT32_MAX))
+ {
+ rc = ERROR_NO_DATA;
+ goto fail;
+ }
+
+ dataLen = (UINT32)(pos - 8);
+ Stream_SetPosition(s, 4);
+ Stream_Write_UINT32(s, dataLen);
+ if (pos > UINT32_MAX)
+ {
+ rc = ERROR_INVALID_DATA;
+ goto fail;
+ }
+
+ status = WTSVirtualChannelWrite(cliprdr->ChannelHandle, (PCHAR)Stream_Buffer(s), (UINT32)pos,
+ &written);
+ rc = status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR;
+fail:
+ Stream_Free(s, TRUE);
+ return rc;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_capabilities(CliprdrServerContext* context,
+ const CLIPRDR_CAPABILITIES* capabilities)
+{
+ size_t offset = 0;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(capabilities);
+
+ CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*)context->handle;
+
+ if (capabilities->common.msgType != CB_CLIP_CAPS)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, capabilities->common.msgType);
+
+ if (capabilities->cCapabilitiesSets > UINT16_MAX)
+ {
+ WLog_ERR(TAG, "Invalid number of capability sets in clipboard caps");
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ s = cliprdr_packet_new(CB_CLIP_CAPS, 0, 4 + CB_CAPSTYPE_GENERAL_LEN);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Write_UINT16(s,
+ (UINT16)capabilities->cCapabilitiesSets); /* cCapabilitiesSets (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad1 (2 bytes) */
+ for (UINT32 x = 0; x < capabilities->cCapabilitiesSets; x++)
+ {
+ const CLIPRDR_CAPABILITY_SET* cap =
+ (const CLIPRDR_CAPABILITY_SET*)(((const BYTE*)capabilities->capabilitySets) + offset);
+ offset += cap->capabilitySetLength;
+
+ switch (cap->capabilitySetType)
+ {
+ case CB_CAPSTYPE_GENERAL:
+ {
+ const CLIPRDR_GENERAL_CAPABILITY_SET* generalCapabilitySet =
+ (const CLIPRDR_GENERAL_CAPABILITY_SET*)cap;
+ Stream_Write_UINT16(
+ s, generalCapabilitySet->capabilitySetType); /* capabilitySetType (2 bytes) */
+ Stream_Write_UINT16(
+ s, generalCapabilitySet->capabilitySetLength); /* lengthCapability (2 bytes) */
+ Stream_Write_UINT32(s, generalCapabilitySet->version); /* version (4 bytes) */
+ Stream_Write_UINT32(
+ s, generalCapabilitySet->generalFlags); /* generalFlags (4 bytes) */
+ }
+ break;
+
+ default:
+ WLog_WARN(TAG, "Unknown capability set type %08" PRIx16, cap->capabilitySetType);
+ if (!Stream_SafeSeek(s, cap->capabilitySetLength))
+ {
+ WLog_ERR(TAG, "short stream");
+ return ERROR_NO_DATA;
+ }
+ break;
+ }
+ }
+ WLog_DBG(TAG, "ServerCapabilities");
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_monitor_ready(CliprdrServerContext* context,
+ const CLIPRDR_MONITOR_READY* monitorReady)
+{
+ wStream* s = NULL;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(monitorReady);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+
+ if (monitorReady->common.msgType != CB_MONITOR_READY)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, monitorReady->common.msgType);
+
+ s = cliprdr_packet_new(CB_MONITOR_READY, monitorReady->common.msgFlags,
+ monitorReady->common.dataLen);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_DBG(TAG, "ServerMonitorReady");
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_format_list(CliprdrServerContext* context,
+ const CLIPRDR_FORMAT_LIST* formatList)
+{
+ wStream* s = NULL;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatList);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+
+ s = cliprdr_packet_format_list_new(formatList, context->useLongFormatNames);
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_format_list_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_DBG(TAG, "ServerFormatList: numFormats: %" PRIu32 "", formatList->numFormats);
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_server_format_list_response(CliprdrServerContext* context,
+ const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse)
+{
+ wStream* s = NULL;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatListResponse);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ if (formatListResponse->common.msgType != CB_FORMAT_LIST_RESPONSE)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, formatListResponse->common.msgType);
+
+ s = cliprdr_packet_new(CB_FORMAT_LIST_RESPONSE, formatListResponse->common.msgFlags,
+ formatListResponse->common.dataLen);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_DBG(TAG, "ServerFormatListResponse");
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_lock_clipboard_data(CliprdrServerContext* context,
+ const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData)
+{
+ wStream* s = NULL;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(lockClipboardData);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ if (lockClipboardData->common.msgType != CB_LOCK_CLIPDATA)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, lockClipboardData->common.msgType);
+
+ s = cliprdr_packet_lock_clipdata_new(lockClipboardData);
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_lock_clipdata_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_DBG(TAG, "ServerLockClipboardData: clipDataId: 0x%08" PRIX32 "",
+ lockClipboardData->clipDataId);
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_server_unlock_clipboard_data(CliprdrServerContext* context,
+ const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
+{
+ wStream* s = NULL;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(unlockClipboardData);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ if (unlockClipboardData->common.msgType != CB_UNLOCK_CLIPDATA)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, unlockClipboardData->common.msgType);
+
+ s = cliprdr_packet_unlock_clipdata_new(unlockClipboardData);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_unlock_clipdata_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_DBG(TAG, "ServerUnlockClipboardData: clipDataId: 0x%08" PRIX32 "",
+ unlockClipboardData->clipDataId);
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_format_data_request(CliprdrServerContext* context,
+ const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest)
+{
+ wStream* s = NULL;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatDataRequest);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ if (formatDataRequest->common.msgType != CB_FORMAT_DATA_REQUEST)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, formatDataRequest->common.msgType);
+
+ s = cliprdr_packet_new(CB_FORMAT_DATA_REQUEST, formatDataRequest->common.msgFlags,
+ formatDataRequest->common.dataLen);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Write_UINT32(s, formatDataRequest->requestedFormatId); /* requestedFormatId (4 bytes) */
+ WLog_DBG(TAG, "ClientFormatDataRequest");
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_server_format_data_response(CliprdrServerContext* context,
+ const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse)
+{
+ wStream* s = NULL;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatDataResponse);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+
+ if (formatDataResponse->common.msgType != CB_FORMAT_DATA_RESPONSE)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, formatDataResponse->common.msgType);
+
+ s = cliprdr_packet_new(CB_FORMAT_DATA_RESPONSE, formatDataResponse->common.msgFlags,
+ formatDataResponse->common.dataLen);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Write(s, formatDataResponse->requestedFormatData, formatDataResponse->common.dataLen);
+ WLog_DBG(TAG, "ServerFormatDataResponse");
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_server_file_contents_request(CliprdrServerContext* context,
+ const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
+{
+ wStream* s = NULL;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(fileContentsRequest);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+
+ if (fileContentsRequest->common.msgType != CB_FILECONTENTS_REQUEST)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, fileContentsRequest->common.msgType);
+
+ s = cliprdr_packet_file_contents_request_new(fileContentsRequest);
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_file_contents_request_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_DBG(TAG, "ServerFileContentsRequest: streamId: 0x%08" PRIX32 "",
+ fileContentsRequest->streamId);
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_server_file_contents_response(CliprdrServerContext* context,
+ const CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse)
+{
+ wStream* s = NULL;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(fileContentsResponse);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+
+ if (fileContentsResponse->common.msgType != CB_FILECONTENTS_RESPONSE)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, fileContentsResponse->common.msgType);
+
+ s = cliprdr_packet_file_contents_response_new(fileContentsResponse);
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_file_contents_response_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_DBG(TAG, "ServerFileContentsResponse: streamId: 0x%08" PRIX32 "",
+ fileContentsResponse->streamId);
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_general_capability(CliprdrServerContext* context, wStream* s,
+ CLIPRDR_GENERAL_CAPABILITY_SET* cap_set)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cap_set);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, cap_set->version); /* version (4 bytes) */
+ Stream_Read_UINT32(s, cap_set->generalFlags); /* generalFlags (4 bytes) */
+
+ if (context->useLongFormatNames)
+ context->useLongFormatNames =
+ (cap_set->generalFlags & CB_USE_LONG_FORMAT_NAMES) ? TRUE : FALSE;
+
+ if (context->streamFileClipEnabled)
+ context->streamFileClipEnabled =
+ (cap_set->generalFlags & CB_STREAM_FILECLIP_ENABLED) ? TRUE : FALSE;
+
+ if (context->fileClipNoFilePaths)
+ context->fileClipNoFilePaths =
+ (cap_set->generalFlags & CB_FILECLIP_NO_FILE_PATHS) ? TRUE : FALSE;
+
+ if (context->canLockClipData)
+ context->canLockClipData = (cap_set->generalFlags & CB_CAN_LOCK_CLIPDATA) ? TRUE : FALSE;
+
+ if (context->hasHugeFileSupport)
+ context->hasHugeFileSupport =
+ (cap_set->generalFlags & CB_HUGE_FILE_SUPPORT_ENABLED) ? TRUE : FALSE;
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_capabilities(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ UINT16 capabilitySetType = 0;
+ UINT16 capabilitySetLength = 0;
+ UINT error = ERROR_INVALID_DATA;
+ size_t cap_sets_size = 0;
+ CLIPRDR_CAPABILITIES capabilities = { 0 };
+ CLIPRDR_CAPABILITY_SET* capSet = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_UNUSED(header);
+
+ WLog_DBG(TAG, "CliprdrClientCapabilities");
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, capabilities.cCapabilitiesSets); /* cCapabilitiesSets (2 bytes) */
+ Stream_Seek_UINT16(s); /* pad1 (2 bytes) */
+
+ for (size_t index = 0; index < capabilities.cCapabilitiesSets; index++)
+ {
+ void* tmp = NULL;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ goto out;
+ Stream_Read_UINT16(s, capabilitySetType); /* capabilitySetType (2 bytes) */
+ Stream_Read_UINT16(s, capabilitySetLength); /* capabilitySetLength (2 bytes) */
+
+ cap_sets_size += capabilitySetLength;
+
+ if (cap_sets_size > 0)
+ tmp = realloc(capabilities.capabilitySets, cap_sets_size);
+ if (tmp == NULL)
+ {
+ WLog_ERR(TAG, "capabilities.capabilitySets realloc failed!");
+ free(capabilities.capabilitySets);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)tmp;
+
+ capSet = &(capabilities.capabilitySets[index]);
+
+ capSet->capabilitySetType = capabilitySetType;
+ capSet->capabilitySetLength = capabilitySetLength;
+
+ switch (capSet->capabilitySetType)
+ {
+ case CB_CAPSTYPE_GENERAL:
+ error = cliprdr_server_receive_general_capability(
+ context, s, (CLIPRDR_GENERAL_CAPABILITY_SET*)capSet);
+ if (error)
+ {
+ WLog_ERR(TAG,
+ "cliprdr_server_receive_general_capability failed with error %" PRIu32
+ "",
+ error);
+ goto out;
+ }
+ break;
+
+ default:
+ WLog_ERR(TAG, "unknown cliprdr capability set: %" PRIu16 "",
+ capSet->capabilitySetType);
+ goto out;
+ }
+ }
+
+ error = CHANNEL_RC_OK;
+ IFCALLRET(context->ClientCapabilities, error, context, &capabilities);
+out:
+ free(capabilities.capabilitySets);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_temporary_directory(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ size_t length = 0;
+ CLIPRDR_TEMP_DIRECTORY tempDirectory = { 0 };
+ CliprdrServerPrivate* cliprdr = NULL;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_UNUSED(header);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s,
+ ARRAYSIZE(cliprdr->temporaryDirectory) * sizeof(WCHAR)))
+ return CHANNEL_RC_NO_MEMORY;
+
+ const WCHAR* wszTempDir = Stream_ConstPointer(s);
+
+ if (wszTempDir[ARRAYSIZE(cliprdr->temporaryDirectory) - 1] != 0)
+ {
+ WLog_ERR(TAG, "wszTempDir[259] was not 0");
+ return ERROR_INVALID_DATA;
+ }
+
+ if (ConvertWCharNToUtf8(wszTempDir, ARRAYSIZE(cliprdr->temporaryDirectory),
+ cliprdr->temporaryDirectory,
+ ARRAYSIZE(cliprdr->temporaryDirectory)) < 0)
+ {
+ WLog_ERR(TAG, "failed to convert temporary directory name");
+ return ERROR_INVALID_DATA;
+ }
+
+ length = strnlen(cliprdr->temporaryDirectory, ARRAYSIZE(cliprdr->temporaryDirectory));
+
+ if (length >= ARRAYSIZE(cliprdr->temporaryDirectory))
+ length = ARRAYSIZE(cliprdr->temporaryDirectory) - 1;
+
+ CopyMemory(tempDirectory.szTempDir, cliprdr->temporaryDirectory, length);
+ tempDirectory.szTempDir[length] = '\0';
+ WLog_DBG(TAG, "CliprdrTemporaryDirectory: %s", cliprdr->temporaryDirectory);
+ IFCALLRET(context->TempDirectory, error, context, &tempDirectory);
+
+ if (error)
+ WLog_ERR(TAG, "TempDirectory failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_format_list(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ CLIPRDR_FORMAT_LIST formatList = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ formatList.common.msgType = CB_FORMAT_LIST;
+ formatList.common.msgFlags = header->msgFlags;
+ formatList.common.dataLen = header->dataLen;
+
+ if ((error = cliprdr_read_format_list(s, &formatList, context->useLongFormatNames)))
+ goto out;
+
+ WLog_DBG(TAG, "ClientFormatList: numFormats: %" PRIu32 "", formatList.numFormats);
+ IFCALLRET(context->ClientFormatList, error, context, &formatList);
+
+ if (error)
+ WLog_ERR(TAG, "ClientFormatList failed with error %" PRIu32 "!", error);
+
+out:
+ cliprdr_free_format_list(&formatList);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_format_list_response(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ WINPR_UNUSED(s);
+ WLog_DBG(TAG, "CliprdrClientFormatListResponse");
+ formatListResponse.common.msgType = CB_FORMAT_LIST_RESPONSE;
+ formatListResponse.common.msgFlags = header->msgFlags;
+ formatListResponse.common.dataLen = header->dataLen;
+ IFCALLRET(context->ClientFormatListResponse, error, context, &formatListResponse);
+
+ if (error)
+ WLog_ERR(TAG, "ClientFormatListResponse failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_lock_clipdata(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ CLIPRDR_LOCK_CLIPBOARD_DATA lockClipboardData = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ WLog_DBG(TAG, "CliprdrClientLockClipData");
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ lockClipboardData.common.msgType = CB_LOCK_CLIPDATA;
+ lockClipboardData.common.msgFlags = header->msgFlags;
+ lockClipboardData.common.dataLen = header->dataLen;
+ Stream_Read_UINT32(s, lockClipboardData.clipDataId); /* clipDataId (4 bytes) */
+ IFCALLRET(context->ClientLockClipboardData, error, context, &lockClipboardData);
+
+ if (error)
+ WLog_ERR(TAG, "ClientLockClipboardData failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_unlock_clipdata(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ CLIPRDR_UNLOCK_CLIPBOARD_DATA unlockClipboardData = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ WLog_DBG(TAG, "CliprdrClientUnlockClipData");
+
+ unlockClipboardData.common.msgType = CB_UNLOCK_CLIPDATA;
+ unlockClipboardData.common.msgFlags = header->msgFlags;
+ unlockClipboardData.common.dataLen = header->dataLen;
+
+ if ((error = cliprdr_read_unlock_clipdata(s, &unlockClipboardData)))
+ return error;
+
+ IFCALLRET(context->ClientUnlockClipboardData, error, context, &unlockClipboardData);
+
+ if (error)
+ WLog_ERR(TAG, "ClientUnlockClipboardData failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_format_data_request(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ WLog_DBG(TAG, "CliprdrClientFormatDataRequest");
+ formatDataRequest.common.msgType = CB_FORMAT_DATA_REQUEST;
+ formatDataRequest.common.msgFlags = header->msgFlags;
+ formatDataRequest.common.dataLen = header->dataLen;
+
+ if ((error = cliprdr_read_format_data_request(s, &formatDataRequest)))
+ return error;
+
+ context->lastRequestedFormatId = formatDataRequest.requestedFormatId;
+ IFCALLRET(context->ClientFormatDataRequest, error, context, &formatDataRequest);
+
+ if (error)
+ WLog_ERR(TAG, "ClientFormatDataRequest failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_format_data_response(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ WLog_DBG(TAG, "CliprdrClientFormatDataResponse");
+ formatDataResponse.common.msgType = CB_FORMAT_DATA_RESPONSE;
+ formatDataResponse.common.msgFlags = header->msgFlags;
+ formatDataResponse.common.dataLen = header->dataLen;
+
+ if ((error = cliprdr_read_format_data_response(s, &formatDataResponse)))
+ return error;
+
+ IFCALLRET(context->ClientFormatDataResponse, error, context, &formatDataResponse);
+
+ if (error)
+ WLog_ERR(TAG, "ClientFormatDataResponse failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_filecontents_request(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ CLIPRDR_FILE_CONTENTS_REQUEST request = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ WLog_DBG(TAG, "CliprdrClientFileContentsRequest");
+ request.common.msgType = CB_FILECONTENTS_REQUEST;
+ request.common.msgFlags = header->msgFlags;
+ request.common.dataLen = header->dataLen;
+
+ if ((error = cliprdr_read_file_contents_request(s, &request)))
+ return error;
+
+ if (!context->hasHugeFileSupport)
+ {
+ if (request.nPositionHigh > 0)
+ return ERROR_INVALID_DATA;
+ if ((UINT64)request.nPositionLow + request.cbRequested > UINT32_MAX)
+ return ERROR_INVALID_DATA;
+ }
+ IFCALLRET(context->ClientFileContentsRequest, error, context, &request);
+
+ if (error)
+ WLog_ERR(TAG, "ClientFileContentsRequest failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_filecontents_response(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ CLIPRDR_FILE_CONTENTS_RESPONSE response = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ WLog_DBG(TAG, "CliprdrClientFileContentsResponse");
+
+ response.common.msgType = CB_FILECONTENTS_RESPONSE;
+ response.common.msgFlags = header->msgFlags;
+ response.common.dataLen = header->dataLen;
+
+ if ((error = cliprdr_read_file_contents_response(s, &response)))
+ return error;
+
+ IFCALLRET(context->ClientFileContentsResponse, error, context, &response);
+
+ if (error)
+ WLog_ERR(TAG, "ClientFileContentsResponse failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_pdu(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ UINT error = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ WLog_DBG(TAG,
+ "CliprdrServerReceivePdu: msgType: %" PRIu16 " msgFlags: 0x%04" PRIX16
+ " dataLen: %" PRIu32 "",
+ header->msgType, header->msgFlags, header->dataLen);
+
+ switch (header->msgType)
+ {
+ case CB_CLIP_CAPS:
+ if ((error = cliprdr_server_receive_capabilities(context, s, header)))
+ WLog_ERR(TAG, "cliprdr_server_receive_capabilities failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_TEMP_DIRECTORY:
+ if ((error = cliprdr_server_receive_temporary_directory(context, s, header)))
+ WLog_ERR(TAG,
+ "cliprdr_server_receive_temporary_directory failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ case CB_FORMAT_LIST:
+ if ((error = cliprdr_server_receive_format_list(context, s, header)))
+ WLog_ERR(TAG, "cliprdr_server_receive_format_list failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_FORMAT_LIST_RESPONSE:
+ if ((error = cliprdr_server_receive_format_list_response(context, s, header)))
+ WLog_ERR(TAG,
+ "cliprdr_server_receive_format_list_response failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ case CB_LOCK_CLIPDATA:
+ if ((error = cliprdr_server_receive_lock_clipdata(context, s, header)))
+ WLog_ERR(TAG, "cliprdr_server_receive_lock_clipdata failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_UNLOCK_CLIPDATA:
+ if ((error = cliprdr_server_receive_unlock_clipdata(context, s, header)))
+ WLog_ERR(TAG,
+ "cliprdr_server_receive_unlock_clipdata failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_FORMAT_DATA_REQUEST:
+ if ((error = cliprdr_server_receive_format_data_request(context, s, header)))
+ WLog_ERR(TAG,
+ "cliprdr_server_receive_format_data_request failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ case CB_FORMAT_DATA_RESPONSE:
+ if ((error = cliprdr_server_receive_format_data_response(context, s, header)))
+ WLog_ERR(TAG,
+ "cliprdr_server_receive_format_data_response failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ case CB_FILECONTENTS_REQUEST:
+ if ((error = cliprdr_server_receive_filecontents_request(context, s, header)))
+ WLog_ERR(TAG,
+ "cliprdr_server_receive_filecontents_request failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ case CB_FILECONTENTS_RESPONSE:
+ if ((error = cliprdr_server_receive_filecontents_response(context, s, header)))
+ WLog_ERR(TAG,
+ "cliprdr_server_receive_filecontents_response failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ default:
+ error = ERROR_INVALID_DATA;
+ WLog_ERR(TAG, "Unexpected clipboard PDU type: %" PRIu16 "", header->msgType);
+ break;
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_init(CliprdrServerContext* context)
+{
+ UINT32 generalFlags = 0;
+ CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet = { 0 };
+ UINT error = 0;
+ CLIPRDR_MONITOR_READY monitorReady = { 0 };
+ CLIPRDR_CAPABILITIES capabilities = { 0 };
+
+ WINPR_ASSERT(context);
+
+ monitorReady.common.msgType = CB_MONITOR_READY;
+ capabilities.common.msgType = CB_CLIP_CAPS;
+
+ if (context->useLongFormatNames)
+ generalFlags |= CB_USE_LONG_FORMAT_NAMES;
+
+ if (context->streamFileClipEnabled)
+ generalFlags |= CB_STREAM_FILECLIP_ENABLED;
+
+ if (context->fileClipNoFilePaths)
+ generalFlags |= CB_FILECLIP_NO_FILE_PATHS;
+
+ if (context->canLockClipData)
+ generalFlags |= CB_CAN_LOCK_CLIPDATA;
+
+ if (context->hasHugeFileSupport)
+ generalFlags |= CB_HUGE_FILE_SUPPORT_ENABLED;
+
+ capabilities.common.msgType = CB_CLIP_CAPS;
+ capabilities.common.msgFlags = 0;
+ capabilities.common.dataLen = 4 + CB_CAPSTYPE_GENERAL_LEN;
+ capabilities.cCapabilitiesSets = 1;
+ capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)&generalCapabilitySet;
+ generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL;
+ generalCapabilitySet.capabilitySetLength = CB_CAPSTYPE_GENERAL_LEN;
+ generalCapabilitySet.version = CB_CAPS_VERSION_2;
+ generalCapabilitySet.generalFlags = generalFlags;
+
+ if ((error = context->ServerCapabilities(context, &capabilities)))
+ {
+ WLog_ERR(TAG, "ServerCapabilities failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ if ((error = context->MonitorReady(context, &monitorReady)))
+ {
+ WLog_ERR(TAG, "MonitorReady failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_read(CliprdrServerContext* context)
+{
+ wStream* s = NULL;
+ size_t position = 0;
+ DWORD BytesToRead = 0;
+ DWORD BytesReturned = 0;
+ CLIPRDR_HEADER header = { 0 };
+ CliprdrServerPrivate* cliprdr = NULL;
+ UINT error = 0;
+ DWORD status = 0;
+
+ WINPR_ASSERT(context);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ s = cliprdr->s;
+
+ if (Stream_GetPosition(s) < CLIPRDR_HEADER_LENGTH)
+ {
+ BytesReturned = 0;
+ BytesToRead = (UINT32)(CLIPRDR_HEADER_LENGTH - Stream_GetPosition(s));
+ status = WaitForSingleObject(cliprdr->ChannelEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ if (status == WAIT_TIMEOUT)
+ return CHANNEL_RC_OK;
+
+ if (!WTSVirtualChannelRead(cliprdr->ChannelHandle, 0, Stream_Pointer(s), BytesToRead,
+ &BytesReturned))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Seek(s, BytesReturned);
+ }
+
+ if (Stream_GetPosition(s) >= CLIPRDR_HEADER_LENGTH)
+ {
+ position = Stream_GetPosition(s);
+ Stream_SetPosition(s, 0);
+ Stream_Read_UINT16(s, header.msgType); /* msgType (2 bytes) */
+ Stream_Read_UINT16(s, header.msgFlags); /* msgFlags (2 bytes) */
+ Stream_Read_UINT32(s, header.dataLen); /* dataLen (4 bytes) */
+
+ if (!Stream_EnsureCapacity(s, (header.dataLen + CLIPRDR_HEADER_LENGTH)))
+ {
+ WLog_ERR(TAG, "Stream_EnsureCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_SetPosition(s, position);
+
+ if (Stream_GetPosition(s) < (header.dataLen + CLIPRDR_HEADER_LENGTH))
+ {
+ BytesReturned = 0;
+ BytesToRead =
+ (UINT32)((header.dataLen + CLIPRDR_HEADER_LENGTH) - Stream_GetPosition(s));
+ status = WaitForSingleObject(cliprdr->ChannelEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ if (status == WAIT_TIMEOUT)
+ return CHANNEL_RC_OK;
+
+ if (!WTSVirtualChannelRead(cliprdr->ChannelHandle, 0, Stream_Pointer(s), BytesToRead,
+ &BytesReturned))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Seek(s, BytesReturned);
+ }
+
+ if (Stream_GetPosition(s) >= (header.dataLen + CLIPRDR_HEADER_LENGTH))
+ {
+ Stream_SetPosition(s, (header.dataLen + CLIPRDR_HEADER_LENGTH));
+ Stream_SealLength(s);
+ Stream_SetPosition(s, CLIPRDR_HEADER_LENGTH);
+
+ if ((error = cliprdr_server_receive_pdu(context, s, &header)))
+ {
+ WLog_ERR(TAG, "cliprdr_server_receive_pdu failed with error code %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ Stream_SetPosition(s, 0);
+ /* check for trailing zero bytes */
+ status = WaitForSingleObject(cliprdr->ChannelEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ if (status == WAIT_TIMEOUT)
+ return CHANNEL_RC_OK;
+
+ BytesReturned = 0;
+ BytesToRead = 4;
+
+ if (!WTSVirtualChannelRead(cliprdr->ChannelHandle, 0, Stream_Pointer(s), BytesToRead,
+ &BytesReturned))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (BytesReturned == 4)
+ {
+ Stream_Read_UINT16(s, header.msgType); /* msgType (2 bytes) */
+ Stream_Read_UINT16(s, header.msgFlags); /* msgFlags (2 bytes) */
+
+ if (!header.msgType)
+ {
+ /* ignore trailing bytes */
+ Stream_SetPosition(s, 0);
+ }
+ }
+ else
+ {
+ Stream_Seek(s, BytesReturned);
+ }
+ }
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static DWORD WINAPI cliprdr_server_thread(LPVOID arg)
+{
+ DWORD status = 0;
+ DWORD nCount = 0;
+ HANDLE events[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ HANDLE ChannelEvent = NULL;
+ CliprdrServerContext* context = (CliprdrServerContext*)arg;
+ CliprdrServerPrivate* cliprdr = NULL;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ ChannelEvent = context->GetEventHandle(context);
+
+ events[nCount++] = cliprdr->StopEvent;
+ events[nCount++] = ChannelEvent;
+
+ if (context->autoInitializationSequence)
+ {
+ if ((error = cliprdr_server_init(context)))
+ {
+ WLog_ERR(TAG, "cliprdr_server_init failed with error %" PRIu32 "!", error);
+ goto out;
+ }
+ }
+
+ while (1)
+ {
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error);
+ goto out;
+ }
+
+ status = WaitForSingleObject(cliprdr->StopEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ goto out;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ status = WaitForSingleObject(ChannelEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ goto out;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ {
+ if ((error = context->CheckEventHandle(context)))
+ {
+ WLog_ERR(TAG, "CheckEventHandle failed with error %" PRIu32 "!", error);
+ break;
+ }
+ }
+ }
+
+out:
+
+ if (error && context->rdpcontext)
+ setChannelError(context->rdpcontext, error, "cliprdr_server_thread reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_open(CliprdrServerContext* context)
+{
+ void* buffer = NULL;
+ DWORD BytesReturned = 0;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ cliprdr->ChannelHandle =
+ WTSVirtualChannelOpen(cliprdr->vcm, WTS_CURRENT_SESSION, CLIPRDR_SVC_CHANNEL_NAME);
+
+ if (!cliprdr->ChannelHandle)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelOpen for cliprdr failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ cliprdr->ChannelEvent = NULL;
+
+ if (WTSVirtualChannelQuery(cliprdr->ChannelHandle, WTSVirtualEventHandle, &buffer,
+ &BytesReturned))
+ {
+ if (BytesReturned != sizeof(HANDLE))
+ {
+ WLog_ERR(TAG, "BytesReturned has not size of HANDLE!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ CopyMemory(&(cliprdr->ChannelEvent), buffer, sizeof(HANDLE));
+ WTSFreeMemory(buffer);
+ }
+
+ if (!cliprdr->ChannelEvent)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelQuery for cliprdr failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_close(CliprdrServerContext* context)
+{
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ if (cliprdr->ChannelHandle)
+ {
+ WTSVirtualChannelClose(cliprdr->ChannelHandle);
+ cliprdr->ChannelHandle = NULL;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_start(CliprdrServerContext* context)
+{
+ UINT error = 0;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ if (!cliprdr->ChannelHandle)
+ {
+ if ((error = context->Open(context)))
+ {
+ WLog_ERR(TAG, "Open failed!");
+ return error;
+ }
+ }
+
+ if (!(cliprdr->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!(cliprdr->Thread = CreateThread(NULL, 0, cliprdr_server_thread, (void*)context, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ CloseHandle(cliprdr->StopEvent);
+ cliprdr->StopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_stop(CliprdrServerContext* context)
+{
+ UINT error = CHANNEL_RC_OK;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ if (cliprdr->StopEvent)
+ {
+ SetEvent(cliprdr->StopEvent);
+
+ if (WaitForSingleObject(cliprdr->Thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ CloseHandle(cliprdr->Thread);
+ CloseHandle(cliprdr->StopEvent);
+ }
+
+ if (cliprdr->ChannelHandle)
+ return context->Close(context);
+
+ return error;
+}
+
+static HANDLE cliprdr_server_get_event_handle(CliprdrServerContext* context)
+{
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ WINPR_ASSERT(cliprdr);
+ return cliprdr->ChannelEvent;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_check_event_handle(CliprdrServerContext* context)
+{
+ return cliprdr_server_read(context);
+}
+
+CliprdrServerContext* cliprdr_server_context_new(HANDLE vcm)
+{
+ CliprdrServerPrivate* cliprdr = NULL;
+ CliprdrServerContext* context = (CliprdrServerContext*)calloc(1, sizeof(CliprdrServerContext));
+
+ if (context)
+ {
+ context->autoInitializationSequence = TRUE;
+ context->Open = cliprdr_server_open;
+ context->Close = cliprdr_server_close;
+ context->Start = cliprdr_server_start;
+ context->Stop = cliprdr_server_stop;
+ context->GetEventHandle = cliprdr_server_get_event_handle;
+ context->CheckEventHandle = cliprdr_server_check_event_handle;
+ context->ServerCapabilities = cliprdr_server_capabilities;
+ context->MonitorReady = cliprdr_server_monitor_ready;
+ context->ServerFormatList = cliprdr_server_format_list;
+ context->ServerFormatListResponse = cliprdr_server_format_list_response;
+ context->ServerLockClipboardData = cliprdr_server_lock_clipboard_data;
+ context->ServerUnlockClipboardData = cliprdr_server_unlock_clipboard_data;
+ context->ServerFormatDataRequest = cliprdr_server_format_data_request;
+ context->ServerFormatDataResponse = cliprdr_server_format_data_response;
+ context->ServerFileContentsRequest = cliprdr_server_file_contents_request;
+ context->ServerFileContentsResponse = cliprdr_server_file_contents_response;
+ cliprdr = context->handle = (CliprdrServerPrivate*)calloc(1, sizeof(CliprdrServerPrivate));
+
+ if (cliprdr)
+ {
+ cliprdr->vcm = vcm;
+ cliprdr->s = Stream_New(NULL, 4096);
+
+ if (!cliprdr->s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ free(context->handle);
+ free(context);
+ return NULL;
+ }
+ }
+ else
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ free(context);
+ return NULL;
+ }
+ }
+
+ return context;
+}
+
+void cliprdr_server_context_free(CliprdrServerContext* context)
+{
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ if (!context)
+ return;
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+
+ if (cliprdr)
+ {
+ Stream_Free(cliprdr->s, TRUE);
+ }
+
+ free(context->handle);
+ free(context);
+}
diff --git a/channels/cliprdr/server/cliprdr_main.h b/channels/cliprdr/server/cliprdr_main.h
new file mode 100644
index 0000000..f2e7162
--- /dev/null
+++ b/channels/cliprdr/server/cliprdr_main.h
@@ -0,0 +1,47 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Clipboard Virtual Channel Extension
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_CLIPRDR_SERVER_MAIN_H
+#define FREERDP_CHANNEL_CLIPRDR_SERVER_MAIN_H
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/stream.h>
+#include <winpr/thread.h>
+
+#include <freerdp/server/cliprdr.h>
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("cliprdr.server")
+
+#define CLIPRDR_HEADER_LENGTH 8
+
+typedef struct
+{
+ HANDLE vcm;
+ HANDLE Thread;
+ HANDLE StopEvent;
+ void* ChannelHandle;
+ HANDLE ChannelEvent;
+
+ wStream* s;
+ char temporaryDirectory[260];
+} CliprdrServerPrivate;
+
+#endif /* FREERDP_CHANNEL_CLIPRDR_SERVER_MAIN_H */
diff --git a/channels/disp/CMakeLists.txt b/channels/disp/CMakeLists.txt
new file mode 100644
index 0000000..44afe99
--- /dev/null
+++ b/channels/disp/CMakeLists.txt
@@ -0,0 +1,26 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("disp")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/disp/ChannelOptions.cmake b/channels/disp/ChannelOptions.cmake
new file mode 100644
index 0000000..0e254ad
--- /dev/null
+++ b/channels/disp/ChannelOptions.cmake
@@ -0,0 +1,12 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT OFF)
+
+define_channel_options(NAME "disp" TYPE "dynamic"
+ DESCRIPTION "Display Update Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPEDISP]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
diff --git a/channels/disp/client/CMakeLists.txt b/channels/disp/client/CMakeLists.txt
new file mode 100644
index 0000000..d1e1f98
--- /dev/null
+++ b/channels/disp/client/CMakeLists.txt
@@ -0,0 +1,32 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("disp")
+
+set(${MODULE_PREFIX}_SRCS
+ disp_main.c
+ disp_main.h
+ ../disp_common.c
+ ../disp_common.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+)
+include_directories(..)
+
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
diff --git a/channels/disp/client/disp_main.c b/channels/disp/client/disp_main.c
new file mode 100644
index 0000000..f018240
--- /dev/null
+++ b/channels/disp/client/disp_main.c
@@ -0,0 +1,323 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Display Update Virtual Channel Extension
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/synch.h>
+#include <winpr/print.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/sysinfo.h>
+#include <winpr/cmdline.h>
+#include <winpr/collections.h>
+
+#include <freerdp/addin.h>
+#include <freerdp/client/channels.h>
+
+#include "disp_main.h"
+#include "../disp_common.h"
+
+typedef struct
+{
+ GENERIC_DYNVC_PLUGIN base;
+
+ DispClientContext* context;
+ UINT32 MaxNumMonitors;
+ UINT32 MaxMonitorAreaFactorA;
+ UINT32 MaxMonitorAreaFactorB;
+} DISP_PLUGIN;
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+disp_send_display_control_monitor_layout_pdu(GENERIC_CHANNEL_CALLBACK* callback, UINT32 NumMonitors,
+ const DISPLAY_CONTROL_MONITOR_LAYOUT* Monitors)
+{
+ UINT status = 0;
+ wStream* s = NULL;
+ DISP_PLUGIN* disp = NULL;
+ UINT32 MonitorLayoutSize = 0;
+ DISPLAY_CONTROL_HEADER header = { 0 };
+
+ WINPR_ASSERT(callback);
+ WINPR_ASSERT(Monitors || (NumMonitors == 0));
+
+ disp = (DISP_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(disp);
+
+ MonitorLayoutSize = DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE;
+ header.length = 8 + 8 + (NumMonitors * MonitorLayoutSize);
+ header.type = DISPLAY_CONTROL_PDU_TYPE_MONITOR_LAYOUT;
+
+ s = Stream_New(NULL, header.length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if ((status = disp_write_header(s, &header)))
+ {
+ WLog_ERR(TAG, "Failed to write header with error %" PRIu32 "!", status);
+ goto out;
+ }
+
+ if (NumMonitors > disp->MaxNumMonitors)
+ NumMonitors = disp->MaxNumMonitors;
+
+ Stream_Write_UINT32(s, MonitorLayoutSize); /* MonitorLayoutSize (4 bytes) */
+ Stream_Write_UINT32(s, NumMonitors); /* NumMonitors (4 bytes) */
+ WLog_DBG(TAG, "NumMonitors=%" PRIu32 "", NumMonitors);
+
+ for (UINT32 index = 0; index < NumMonitors; index++)
+ {
+ DISPLAY_CONTROL_MONITOR_LAYOUT current = Monitors[index];
+ current.Width -= (current.Width % 2);
+
+ if (current.Width < 200)
+ current.Width = 200;
+
+ if (current.Width > 8192)
+ current.Width = 8192;
+
+ if (current.Width % 2)
+ current.Width++;
+
+ if (current.Height < 200)
+ current.Height = 200;
+
+ if (current.Height > 8192)
+ current.Height = 8192;
+
+ Stream_Write_UINT32(s, current.Flags); /* Flags (4 bytes) */
+ Stream_Write_UINT32(s, current.Left); /* Left (4 bytes) */
+ Stream_Write_UINT32(s, current.Top); /* Top (4 bytes) */
+ Stream_Write_UINT32(s, current.Width); /* Width (4 bytes) */
+ Stream_Write_UINT32(s, current.Height); /* Height (4 bytes) */
+ Stream_Write_UINT32(s, current.PhysicalWidth); /* PhysicalWidth (4 bytes) */
+ Stream_Write_UINT32(s, current.PhysicalHeight); /* PhysicalHeight (4 bytes) */
+ Stream_Write_UINT32(s, current.Orientation); /* Orientation (4 bytes) */
+ Stream_Write_UINT32(s, current.DesktopScaleFactor); /* DesktopScaleFactor (4 bytes) */
+ Stream_Write_UINT32(s, current.DeviceScaleFactor); /* DeviceScaleFactor (4 bytes) */
+ WLog_DBG(TAG,
+ "\t%" PRIu32 " : Flags: 0x%08" PRIX32 " Left/Top: (%" PRId32 ",%" PRId32
+ ") W/H=%" PRIu32 "x%" PRIu32 ")",
+ index, current.Flags, current.Left, current.Top, current.Width, current.Height);
+ WLog_DBG(TAG,
+ "\t PhysicalWidth: %" PRIu32 " PhysicalHeight: %" PRIu32 " Orientation: %" PRIu32
+ "",
+ current.PhysicalWidth, current.PhysicalHeight, current.Orientation);
+ }
+
+out:
+ Stream_SealLength(s);
+ status = callback->channel->Write(callback->channel, (UINT32)Stream_Length(s), Stream_Buffer(s),
+ NULL);
+ Stream_Free(s, TRUE);
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT disp_recv_display_control_caps_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ DISP_PLUGIN* disp = NULL;
+ DispClientContext* context = NULL;
+ UINT ret = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(callback);
+ WINPR_ASSERT(s);
+
+ disp = (DISP_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(disp);
+
+ context = disp->context;
+ WINPR_ASSERT(context);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, disp->MaxNumMonitors); /* MaxNumMonitors (4 bytes) */
+ Stream_Read_UINT32(s, disp->MaxMonitorAreaFactorA); /* MaxMonitorAreaFactorA (4 bytes) */
+ Stream_Read_UINT32(s, disp->MaxMonitorAreaFactorB); /* MaxMonitorAreaFactorB (4 bytes) */
+
+ if (context->DisplayControlCaps)
+ ret = context->DisplayControlCaps(context, disp->MaxNumMonitors,
+ disp->MaxMonitorAreaFactorA, disp->MaxMonitorAreaFactorB);
+
+ return ret;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT disp_recv_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ UINT32 error = 0;
+ DISPLAY_CONTROL_HEADER header = { 0 };
+
+ WINPR_ASSERT(callback);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ if ((error = disp_read_header(s, &header)))
+ {
+ WLog_ERR(TAG, "disp_read_header failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, header.length))
+ {
+ WLog_ERR(TAG, "not enough remaining data");
+ return ERROR_INVALID_DATA;
+ }
+
+ switch (header.type)
+ {
+ case DISPLAY_CONTROL_PDU_TYPE_CAPS:
+ return disp_recv_display_control_caps_pdu(callback, s);
+
+ default:
+ WLog_ERR(TAG, "Type %" PRIu32 " not recognized!", header.type);
+ return ERROR_INTERNAL_ERROR;
+ }
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT disp_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+ return disp_recv_pdu(callback, data);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT disp_on_close(IWTSVirtualChannelCallback* pChannelCallback)
+{
+ free(pChannelCallback);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Channel Client Interface
+ */
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT disp_send_monitor_layout(DispClientContext* context, UINT32 NumMonitors,
+ DISPLAY_CONTROL_MONITOR_LAYOUT* Monitors)
+{
+ DISP_PLUGIN* disp = NULL;
+ GENERIC_CHANNEL_CALLBACK* callback = NULL;
+
+ WINPR_ASSERT(context);
+
+ disp = (DISP_PLUGIN*)context->handle;
+ WINPR_ASSERT(disp);
+
+ callback = disp->base.listener_callback->channel_callback;
+
+ return disp_send_display_control_monitor_layout_pdu(callback, NumMonitors, Monitors);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT disp_plugin_initialize(GENERIC_DYNVC_PLUGIN* base, rdpContext* rcontext,
+ rdpSettings* settings)
+{
+ DispClientContext* context = NULL;
+ DISP_PLUGIN* disp = (DISP_PLUGIN*)base;
+
+ WINPR_ASSERT(disp);
+ disp->MaxNumMonitors = 16;
+ disp->MaxMonitorAreaFactorA = 8192;
+ disp->MaxMonitorAreaFactorB = 8192;
+
+ context = (DispClientContext*)calloc(1, sizeof(DispClientContext));
+ if (!context)
+ {
+ WLog_Print(base->log, WLOG_ERROR, "unable to allocate DispClientContext");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ context->handle = (void*)disp;
+ context->SendMonitorLayout = disp_send_monitor_layout;
+
+ disp->base.iface.pInterface = disp->context = context;
+
+ return CHANNEL_RC_OK;
+}
+
+static void disp_plugin_terminated(GENERIC_DYNVC_PLUGIN* base)
+{
+ DISP_PLUGIN* disp = (DISP_PLUGIN*)base;
+
+ WINPR_ASSERT(disp);
+
+ free(disp->context);
+}
+
+static const IWTSVirtualChannelCallback disp_callbacks = { disp_on_data_received, NULL, /* Open */
+ disp_on_close, NULL };
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT disp_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
+{
+ return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, DISP_DVC_CHANNEL_NAME,
+ sizeof(DISP_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK),
+ &disp_callbacks, disp_plugin_initialize,
+ disp_plugin_terminated);
+}
diff --git a/channels/disp/client/disp_main.h b/channels/disp/client/disp_main.h
new file mode 100644
index 0000000..d5ce446
--- /dev/null
+++ b/channels/disp/client/disp_main.h
@@ -0,0 +1,36 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Display Update Virtual Channel Extension
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_DISP_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_DISP_CLIENT_MAIN_H
+
+#include <freerdp/config.h>
+
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/log.h>
+
+#include <freerdp/client/disp.h>
+
+#define TAG CHANNELS_TAG("disp.client")
+
+#endif /* FREERDP_CHANNEL_DISP_CLIENT_MAIN_H */
diff --git a/channels/disp/disp_common.c b/channels/disp/disp_common.c
new file mode 100644
index 0000000..9387691
--- /dev/null
+++ b/channels/disp/disp_common.c
@@ -0,0 +1,55 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDPEDISP Virtual Channel Extension
+ *
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("disp.common")
+
+#include "disp_common.h"
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT disp_read_header(wStream* s, DISPLAY_CONTROL_HEADER* header)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, header->type);
+ Stream_Read_UINT32(s, header->length);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT disp_write_header(wStream* s, const DISPLAY_CONTROL_HEADER* header)
+{
+ Stream_Write_UINT32(s, header->type);
+ Stream_Write_UINT32(s, header->length);
+ return CHANNEL_RC_OK;
+}
diff --git a/channels/disp/disp_common.h b/channels/disp/disp_common.h
new file mode 100644
index 0000000..386b8b3
--- /dev/null
+++ b/channels/disp/disp_common.h
@@ -0,0 +1,32 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDPEDISP Virtual Channel Extension
+ *
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_DISP_COMMON_H
+#define FREERDP_CHANNEL_DISP_COMMON_H
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+
+#include <freerdp/channels/disp.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL UINT disp_read_header(wStream* s, DISPLAY_CONTROL_HEADER* header);
+FREERDP_LOCAL UINT disp_write_header(wStream* s, const DISPLAY_CONTROL_HEADER* header);
+
+#endif /* FREERDP_CHANNEL_DISP_COMMON_H */
diff --git a/channels/disp/server/CMakeLists.txt b/channels/disp/server/CMakeLists.txt
new file mode 100644
index 0000000..3c0e3b6
--- /dev/null
+++ b/channels/disp/server/CMakeLists.txt
@@ -0,0 +1,32 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_server("disp")
+
+set(${MODULE_PREFIX}_SRCS
+ disp_main.c
+ disp_main.h
+ ../disp_common.c
+ ../disp_common.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+include_directories(..)
+
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry")
diff --git a/channels/disp/server/disp_main.c b/channels/disp/server/disp_main.c
new file mode 100644
index 0000000..2bb6e7b
--- /dev/null
+++ b/channels/disp/server/disp_main.c
@@ -0,0 +1,632 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDPEDISP Virtual Channel Extension
+ *
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include "disp_main.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/sysinfo.h>
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/log.h>
+
+#include <freerdp/server/disp.h>
+#include "../disp_common.h"
+
+#define TAG CHANNELS_TAG("rdpedisp.server")
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+
+static wStream* disp_server_single_packet_new(UINT32 type, UINT32 length)
+{
+ UINT error = 0;
+ DISPLAY_CONTROL_HEADER header;
+ wStream* s = Stream_New(NULL, DISPLAY_CONTROL_HEADER_LENGTH + length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ goto error;
+ }
+
+ header.type = type;
+ header.length = DISPLAY_CONTROL_HEADER_LENGTH + length;
+
+ if ((error = disp_write_header(s, &header)))
+ {
+ WLog_ERR(TAG, "Failed to write header with error %" PRIu32 "!", error);
+ goto error;
+ }
+
+ return s;
+error:
+ Stream_Free(s, TRUE);
+ return NULL;
+}
+
+static void disp_server_sanitize_monitor_layout(DISPLAY_CONTROL_MONITOR_LAYOUT* monitor)
+{
+ if (monitor->PhysicalWidth < DISPLAY_CONTROL_MIN_PHYSICAL_MONITOR_WIDTH ||
+ monitor->PhysicalWidth > DISPLAY_CONTROL_MAX_PHYSICAL_MONITOR_WIDTH ||
+ monitor->PhysicalHeight < DISPLAY_CONTROL_MIN_PHYSICAL_MONITOR_HEIGHT ||
+ monitor->PhysicalHeight > DISPLAY_CONTROL_MAX_PHYSICAL_MONITOR_HEIGHT)
+ {
+ if (monitor->PhysicalWidth != 0 || monitor->PhysicalHeight != 0)
+ WLog_DBG(
+ TAG,
+ "Sanitizing invalid physical monitor size. Old physical monitor size: [%" PRIu32
+ ", %" PRIu32 "]",
+ monitor->PhysicalWidth, monitor->PhysicalHeight);
+
+ monitor->PhysicalWidth = monitor->PhysicalHeight = 0;
+ }
+}
+
+static BOOL disp_server_is_monitor_layout_valid(const DISPLAY_CONTROL_MONITOR_LAYOUT* monitor)
+{
+ WINPR_ASSERT(monitor);
+
+ if (monitor->Width < DISPLAY_CONTROL_MIN_MONITOR_WIDTH ||
+ monitor->Width > DISPLAY_CONTROL_MAX_MONITOR_WIDTH)
+ {
+ WLog_WARN(TAG, "Received invalid value for monitor->Width: %" PRIu32 "", monitor->Width);
+ return FALSE;
+ }
+
+ if (monitor->Height < DISPLAY_CONTROL_MIN_MONITOR_HEIGHT ||
+ monitor->Height > DISPLAY_CONTROL_MAX_MONITOR_HEIGHT)
+ {
+ WLog_WARN(TAG, "Received invalid value for monitor->Height: %" PRIu32 "", monitor->Width);
+ return FALSE;
+ }
+
+ switch (monitor->Orientation)
+ {
+ case ORIENTATION_LANDSCAPE:
+ case ORIENTATION_PORTRAIT:
+ case ORIENTATION_LANDSCAPE_FLIPPED:
+ case ORIENTATION_PORTRAIT_FLIPPED:
+ break;
+
+ default:
+ WLog_WARN(TAG, "Received incorrect value for monitor->Orientation: %" PRIu32 "",
+ monitor->Orientation);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static UINT disp_recv_display_control_monitor_layout_pdu(wStream* s, DispServerContext* context)
+{
+ UINT32 error = CHANNEL_RC_OK;
+ DISPLAY_CONTROL_MONITOR_LAYOUT_PDU pdu = { 0 };
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(context);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, pdu.MonitorLayoutSize); /* MonitorLayoutSize (4 bytes) */
+
+ if (pdu.MonitorLayoutSize != DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE)
+ {
+ WLog_ERR(TAG, "MonitorLayoutSize is set to %" PRIu32 ". expected %" PRIu32 "",
+ pdu.MonitorLayoutSize, DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE);
+ return ERROR_INVALID_DATA;
+ }
+
+ Stream_Read_UINT32(s, pdu.NumMonitors); /* NumMonitors (4 bytes) */
+
+ if (pdu.NumMonitors > context->MaxNumMonitors)
+ {
+ WLog_ERR(TAG, "NumMonitors (%" PRIu32 ")> server MaxNumMonitors (%" PRIu32 ")",
+ pdu.NumMonitors, context->MaxNumMonitors);
+ return ERROR_INVALID_DATA;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, pdu.NumMonitors,
+ DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE))
+ return ERROR_INVALID_DATA;
+
+ pdu.Monitors = (DISPLAY_CONTROL_MONITOR_LAYOUT*)calloc(pdu.NumMonitors,
+ sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT));
+
+ if (!pdu.Monitors)
+ {
+ WLog_ERR(TAG, "disp_recv_display_control_monitor_layout_pdu(): calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ WLog_DBG(TAG, "disp_recv_display_control_monitor_layout_pdu: NumMonitors=%" PRIu32 "",
+ pdu.NumMonitors);
+
+ for (UINT32 index = 0; index < pdu.NumMonitors; index++)
+ {
+ DISPLAY_CONTROL_MONITOR_LAYOUT* monitor = &(pdu.Monitors[index]);
+
+ Stream_Read_UINT32(s, monitor->Flags); /* Flags (4 bytes) */
+ Stream_Read_UINT32(s, monitor->Left); /* Left (4 bytes) */
+ Stream_Read_UINT32(s, monitor->Top); /* Top (4 bytes) */
+ Stream_Read_UINT32(s, monitor->Width); /* Width (4 bytes) */
+ Stream_Read_UINT32(s, monitor->Height); /* Height (4 bytes) */
+ Stream_Read_UINT32(s, monitor->PhysicalWidth); /* PhysicalWidth (4 bytes) */
+ Stream_Read_UINT32(s, monitor->PhysicalHeight); /* PhysicalHeight (4 bytes) */
+ Stream_Read_UINT32(s, monitor->Orientation); /* Orientation (4 bytes) */
+ Stream_Read_UINT32(s, monitor->DesktopScaleFactor); /* DesktopScaleFactor (4 bytes) */
+ Stream_Read_UINT32(s, monitor->DeviceScaleFactor); /* DeviceScaleFactor (4 bytes) */
+
+ disp_server_sanitize_monitor_layout(monitor);
+ WLog_DBG(TAG,
+ "\t%" PRIu32 " : Flags: 0x%08" PRIX32 " Left/Top: (%" PRId32 ",%" PRId32
+ ") W/H=%" PRIu32 "x%" PRIu32 ")",
+ index, monitor->Flags, monitor->Left, monitor->Top, monitor->Width,
+ monitor->Height);
+ WLog_DBG(TAG,
+ "\t PhysicalWidth: %" PRIu32 " PhysicalHeight: %" PRIu32 " Orientation: %" PRIu32
+ "",
+ monitor->PhysicalWidth, monitor->PhysicalHeight, monitor->Orientation);
+
+ if (!disp_server_is_monitor_layout_valid(monitor))
+ {
+ error = ERROR_INVALID_DATA;
+ goto out;
+ }
+ }
+
+ if (context)
+ IFCALLRET(context->DispMonitorLayout, error, context, &pdu);
+
+out:
+ free(pdu.Monitors);
+ return error;
+}
+
+static UINT disp_server_receive_pdu(DispServerContext* context, wStream* s)
+{
+ UINT error = CHANNEL_RC_OK;
+ size_t beg = 0;
+ size_t end = 0;
+ DISPLAY_CONTROL_HEADER header = { 0 };
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(context);
+
+ beg = Stream_GetPosition(s);
+
+ if ((error = disp_read_header(s, &header)))
+ {
+ WLog_ERR(TAG, "disp_read_header failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ switch (header.type)
+ {
+ case DISPLAY_CONTROL_PDU_TYPE_MONITOR_LAYOUT:
+ if ((error = disp_recv_display_control_monitor_layout_pdu(s, context)))
+ WLog_ERR(TAG,
+ "disp_recv_display_control_monitor_layout_pdu "
+ "failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ default:
+ error = CHANNEL_RC_BAD_PROC;
+ WLog_WARN(TAG, "Received unknown PDU type: %" PRIu32 "", header.type);
+ break;
+ }
+
+ end = Stream_GetPosition(s);
+
+ if (end != (beg + header.length))
+ {
+ WLog_ERR(TAG, "Unexpected DISP pdu end: Actual: %" PRIuz ", Expected: %" PRIuz "", end,
+ (beg + header.length));
+ Stream_SetPosition(s, (beg + header.length));
+ }
+
+ return error;
+}
+
+static UINT disp_server_handle_messages(DispServerContext* context)
+{
+ DWORD BytesReturned = 0;
+ void* buffer = NULL;
+ UINT ret = CHANNEL_RC_OK;
+ DispServerPrivate* priv = NULL;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+
+ priv = context->priv;
+ WINPR_ASSERT(priv);
+
+ s = priv->input_stream;
+ WINPR_ASSERT(s);
+
+ /* Check whether the dynamic channel is ready */
+ if (!priv->isReady)
+ {
+ if (WTSVirtualChannelQuery(priv->disp_channel, WTSVirtualChannelReady, &buffer,
+ &BytesReturned) == FALSE)
+ {
+ if (GetLastError() == ERROR_NO_DATA)
+ return ERROR_NO_DATA;
+
+ WLog_ERR(TAG, "WTSVirtualChannelQuery failed");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ priv->isReady = *((BOOL*)buffer);
+ WTSFreeMemory(buffer);
+ }
+
+ /* Consume channel event only after the disp dynamic channel is ready */
+ Stream_SetPosition(s, 0);
+
+ if (!WTSVirtualChannelRead(priv->disp_channel, 0, NULL, 0, &BytesReturned))
+ {
+ if (GetLastError() == ERROR_NO_DATA)
+ return ERROR_NO_DATA;
+
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (BytesReturned < 1)
+ return CHANNEL_RC_OK;
+
+ if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if (WTSVirtualChannelRead(priv->disp_channel, 0, (PCHAR)Stream_Buffer(s), Stream_Capacity(s),
+ &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_SetLength(s, BytesReturned);
+ Stream_SetPosition(s, 0);
+
+ while (Stream_GetPosition(s) < Stream_Length(s))
+ {
+ if ((ret = disp_server_receive_pdu(context, s)))
+ {
+ WLog_ERR(TAG,
+ "disp_server_receive_pdu "
+ "failed with error %" PRIu32 "!",
+ ret);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+static DWORD WINAPI disp_server_thread_func(LPVOID arg)
+{
+ DispServerContext* context = (DispServerContext*)arg;
+ DispServerPrivate* priv = NULL;
+ DWORD status = 0;
+ DWORD nCount = 0;
+ HANDLE events[8] = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+
+ priv = context->priv;
+ WINPR_ASSERT(priv);
+
+ events[nCount++] = priv->stopEvent;
+ events[nCount++] = priv->channelEvent;
+
+ /* Main virtual channel loop. RDPEDISP do not need version negotiation */
+ while (TRUE)
+ {
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error);
+ break;
+ }
+
+ /* Stop Event */
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ if ((error = disp_server_handle_messages(context)))
+ {
+ WLog_ERR(TAG, "disp_server_handle_messages failed with error %" PRIu32 "", error);
+ break;
+ }
+ }
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT disp_server_open(DispServerContext* context)
+{
+ UINT rc = ERROR_INTERNAL_ERROR;
+ DispServerPrivate* priv = NULL;
+ DWORD BytesReturned = 0;
+ PULONG pSessionId = NULL;
+ void* buffer = NULL;
+ UINT32 channelId = 0;
+ BOOL status = TRUE;
+
+ WINPR_ASSERT(context);
+
+ priv = context->priv;
+ WINPR_ASSERT(priv);
+
+ priv->SessionId = WTS_CURRENT_SESSION;
+
+ if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId,
+ (LPSTR*)&pSessionId, &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSQuerySessionInformationA failed!");
+ rc = ERROR_INTERNAL_ERROR;
+ goto out_close;
+ }
+
+ priv->SessionId = (DWORD)*pSessionId;
+ WTSFreeMemory(pSessionId);
+ priv->disp_channel = (HANDLE)WTSVirtualChannelOpenEx(priv->SessionId, DISP_DVC_CHANNEL_NAME,
+ WTS_CHANNEL_OPTION_DYNAMIC);
+
+ if (!priv->disp_channel)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed!");
+ rc = GetLastError();
+ goto out_close;
+ }
+
+ channelId = WTSChannelGetIdByHandle(priv->disp_channel);
+
+ IFCALLRET(context->ChannelIdAssigned, status, context, channelId);
+ if (!status)
+ {
+ WLog_ERR(TAG, "context->ChannelIdAssigned failed!");
+ rc = ERROR_INTERNAL_ERROR;
+ goto out_close;
+ }
+
+ /* Query for channel event handle */
+ if (!WTSVirtualChannelQuery(priv->disp_channel, WTSVirtualEventHandle, &buffer,
+ &BytesReturned) ||
+ (BytesReturned != sizeof(HANDLE)))
+ {
+ WLog_ERR(TAG,
+ "WTSVirtualChannelQuery failed "
+ "or invalid returned size(%" PRIu32 ")",
+ BytesReturned);
+
+ if (buffer)
+ WTSFreeMemory(buffer);
+
+ rc = ERROR_INTERNAL_ERROR;
+ goto out_close;
+ }
+
+ CopyMemory(&priv->channelEvent, buffer, sizeof(HANDLE));
+ WTSFreeMemory(buffer);
+
+ if (priv->thread == NULL)
+ {
+ if (!(priv->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ rc = ERROR_INTERNAL_ERROR;
+ goto out_close;
+ }
+
+ if (!(priv->thread =
+ CreateThread(NULL, 0, disp_server_thread_func, (void*)context, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ CloseHandle(priv->stopEvent);
+ priv->stopEvent = NULL;
+ rc = ERROR_INTERNAL_ERROR;
+ goto out_close;
+ }
+ }
+
+ return CHANNEL_RC_OK;
+out_close:
+ WTSVirtualChannelClose(priv->disp_channel);
+ priv->disp_channel = NULL;
+ priv->channelEvent = NULL;
+ return rc;
+}
+
+static UINT disp_server_packet_send(DispServerContext* context, wStream* s)
+{
+ UINT ret = 0;
+ ULONG written = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(s);
+
+ if (!WTSVirtualChannelWrite(context->priv->disp_channel, (PCHAR)Stream_Buffer(s),
+ Stream_GetPosition(s), &written))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
+ ret = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ if (written < Stream_GetPosition(s))
+ {
+ WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written,
+ Stream_GetPosition(s));
+ }
+
+ ret = CHANNEL_RC_OK;
+out:
+ Stream_Free(s, TRUE);
+ return ret;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT disp_server_send_caps_pdu(DispServerContext* context)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+
+ s = disp_server_single_packet_new(DISPLAY_CONTROL_PDU_TYPE_CAPS, 12);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "disp_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT32(s, context->MaxNumMonitors); /* MaxNumMonitors (4 bytes) */
+ Stream_Write_UINT32(s, context->MaxMonitorAreaFactorA); /* MaxMonitorAreaFactorA (4 bytes) */
+ Stream_Write_UINT32(s, context->MaxMonitorAreaFactorB); /* MaxMonitorAreaFactorB (4 bytes) */
+ return disp_server_packet_send(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT disp_server_close(DispServerContext* context)
+{
+ UINT error = CHANNEL_RC_OK;
+ DispServerPrivate* priv = NULL;
+
+ WINPR_ASSERT(context);
+
+ priv = context->priv;
+ WINPR_ASSERT(priv);
+
+ if (priv->thread)
+ {
+ SetEvent(priv->stopEvent);
+
+ if (WaitForSingleObject(priv->thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ CloseHandle(priv->thread);
+ CloseHandle(priv->stopEvent);
+ priv->thread = NULL;
+ priv->stopEvent = NULL;
+ }
+
+ if (priv->disp_channel)
+ {
+ WTSVirtualChannelClose(priv->disp_channel);
+ priv->disp_channel = NULL;
+ }
+
+ return error;
+}
+
+DispServerContext* disp_server_context_new(HANDLE vcm)
+{
+ DispServerContext* context = NULL;
+ DispServerPrivate* priv = NULL;
+ context = (DispServerContext*)calloc(1, sizeof(DispServerContext));
+
+ if (!context)
+ {
+ WLog_ERR(TAG, "disp_server_context_new(): calloc DispServerContext failed!");
+ goto fail;
+ }
+
+ priv = context->priv = (DispServerPrivate*)calloc(1, sizeof(DispServerPrivate));
+
+ if (!context->priv)
+ {
+ WLog_ERR(TAG, "disp_server_context_new(): calloc DispServerPrivate failed!");
+ goto fail;
+ }
+
+ priv->input_stream = Stream_New(NULL, 4);
+
+ if (!priv->input_stream)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ goto fail;
+ }
+
+ context->vcm = vcm;
+ context->Open = disp_server_open;
+ context->Close = disp_server_close;
+ context->DisplayControlCaps = disp_server_send_caps_pdu;
+ priv->isReady = FALSE;
+ return context;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ disp_server_context_free(context);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void disp_server_context_free(DispServerContext* context)
+{
+ if (!context)
+ return;
+
+ if (context->priv)
+ {
+ disp_server_close(context);
+ Stream_Free(context->priv->input_stream, TRUE);
+ free(context->priv);
+ }
+
+ free(context);
+}
diff --git a/channels/disp/server/disp_main.h b/channels/disp/server/disp_main.h
new file mode 100644
index 0000000..920a508
--- /dev/null
+++ b/channels/disp/server/disp_main.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDPEDISP Virtual Channel Extension
+ *
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_DISP_SERVER_MAIN_H
+#define FREERDP_CHANNEL_DISP_SERVER_MAIN_H
+
+#include <freerdp/server/disp.h>
+
+struct s_disp_server_private
+{
+ BOOL isReady;
+ wStream* input_stream;
+ HANDLE channelEvent;
+ HANDLE thread;
+ HANDLE stopEvent;
+ DWORD SessionId;
+
+ void* disp_channel;
+};
+
+#endif /* FREERDP_CHANNEL_DISP_SERVER_MAIN_H */
diff --git a/channels/drdynvc/CMakeLists.txt b/channels/drdynvc/CMakeLists.txt
new file mode 100644
index 0000000..9a6ee1f
--- /dev/null
+++ b/channels/drdynvc/CMakeLists.txt
@@ -0,0 +1,26 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("drdynvc")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/drdynvc/ChannelOptions.cmake b/channels/drdynvc/ChannelOptions.cmake
new file mode 100644
index 0000000..76376b6
--- /dev/null
+++ b/channels/drdynvc/ChannelOptions.cmake
@@ -0,0 +1,13 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT ON)
+
+define_channel_options(NAME "drdynvc" TYPE "static"
+ DESCRIPTION "Dynamic Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPEDYC]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/drdynvc/client/CMakeLists.txt b/channels/drdynvc/client/CMakeLists.txt
new file mode 100644
index 0000000..7f8f04a
--- /dev/null
+++ b/channels/drdynvc/client/CMakeLists.txt
@@ -0,0 +1,25 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("drdynvc")
+
+set(${MODULE_PREFIX}_SRCS
+ drdynvc_main.c
+ drdynvc_main.h
+)
+
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx")
diff --git a/channels/drdynvc/client/drdynvc_main.c b/channels/drdynvc/client/drdynvc_main.c
new file mode 100644
index 0000000..0b85c0f
--- /dev/null
+++ b/channels/drdynvc/client/drdynvc_main.c
@@ -0,0 +1,2046 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Dynamic Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+#include <winpr/interlocked.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/utils/drdynvc.h>
+
+#include "drdynvc_main.h"
+
+#define TAG CHANNELS_TAG("drdynvc.client")
+
+static void dvcman_channel_free(DVCMAN_CHANNEL* channel);
+static UINT dvcman_channel_close(DVCMAN_CHANNEL* channel, BOOL perRequest, BOOL fromHashTableFn);
+static void dvcman_free(drdynvcPlugin* drdynvc, IWTSVirtualChannelManager* pChannelMgr);
+static UINT drdynvc_write_data(drdynvcPlugin* drdynvc, UINT32 ChannelId, const BYTE* data,
+ UINT32 dataSize, BOOL* close);
+static UINT drdynvc_send(drdynvcPlugin* drdynvc, wStream* s);
+
+static void dvcman_wtslistener_free(DVCMAN_LISTENER* listener)
+{
+ if (listener)
+ free(listener->channel_name);
+ free(listener);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT dvcman_get_configuration(IWTSListener* pListener, void** ppPropertyBag)
+{
+ WINPR_ASSERT(ppPropertyBag);
+ WINPR_UNUSED(pListener);
+ *ppPropertyBag = NULL;
+ return ERROR_INTERNAL_ERROR;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT dvcman_create_listener(IWTSVirtualChannelManager* pChannelMgr,
+ const char* pszChannelName, ULONG ulFlags,
+ IWTSListenerCallback* pListenerCallback,
+ IWTSListener** ppListener)
+{
+ DVCMAN* dvcman = (DVCMAN*)pChannelMgr;
+ DVCMAN_LISTENER* listener = NULL;
+
+ WINPR_ASSERT(dvcman);
+ WLog_DBG(TAG, "create_listener: %" PRIuz ".%s.", HashTable_Count(dvcman->listeners) + 1,
+ pszChannelName);
+ listener = (DVCMAN_LISTENER*)calloc(1, sizeof(DVCMAN_LISTENER));
+
+ if (!listener)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ listener->iface.GetConfiguration = dvcman_get_configuration;
+ listener->iface.pInterface = NULL;
+ listener->dvcman = dvcman;
+ listener->channel_name = _strdup(pszChannelName);
+
+ if (!listener->channel_name)
+ {
+ WLog_ERR(TAG, "_strdup failed!");
+ dvcman_wtslistener_free(listener);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ listener->flags = ulFlags;
+ listener->listener_callback = pListenerCallback;
+
+ if (ppListener)
+ *ppListener = (IWTSListener*)listener;
+
+ if (!HashTable_Insert(dvcman->listeners, listener->channel_name, listener))
+ {
+ dvcman_wtslistener_free(listener);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert takes ownership of listener
+ return CHANNEL_RC_OK;
+}
+
+static UINT dvcman_destroy_listener(IWTSVirtualChannelManager* pChannelMgr, IWTSListener* pListener)
+{
+ DVCMAN_LISTENER* listener = (DVCMAN_LISTENER*)pListener;
+
+ WINPR_UNUSED(pChannelMgr);
+
+ if (listener)
+ {
+ DVCMAN* dvcman = listener->dvcman;
+ if (dvcman)
+ HashTable_Remove(dvcman->listeners, listener->channel_name);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT dvcman_register_plugin(IDRDYNVC_ENTRY_POINTS* pEntryPoints, const char* name,
+ IWTSPlugin* pPlugin)
+{
+ WINPR_ASSERT(pEntryPoints);
+ DVCMAN* dvcman = ((DVCMAN_ENTRY_POINTS*)pEntryPoints)->dvcman;
+
+ WINPR_ASSERT(dvcman);
+ if (!ArrayList_Append(dvcman->plugin_names, name))
+ return ERROR_INTERNAL_ERROR;
+ if (!ArrayList_Append(dvcman->plugins, pPlugin))
+ return ERROR_INTERNAL_ERROR;
+
+ WLog_DBG(TAG, "register_plugin: num_plugins %" PRIuz, ArrayList_Count(dvcman->plugins));
+ return CHANNEL_RC_OK;
+}
+
+static IWTSPlugin* dvcman_get_plugin(IDRDYNVC_ENTRY_POINTS* pEntryPoints, const char* name)
+{
+ IWTSPlugin* plugin = NULL;
+ size_t nc = 0;
+ size_t pc = 0;
+ WINPR_ASSERT(pEntryPoints);
+ DVCMAN* dvcman = ((DVCMAN_ENTRY_POINTS*)pEntryPoints)->dvcman;
+ if (!dvcman || !pEntryPoints || !name)
+ return NULL;
+
+ nc = ArrayList_Count(dvcman->plugin_names);
+ pc = ArrayList_Count(dvcman->plugins);
+ if (nc != pc)
+ return NULL;
+
+ ArrayList_Lock(dvcman->plugin_names);
+ ArrayList_Lock(dvcman->plugins);
+ for (size_t i = 0; i < pc; i++)
+ {
+ const char* cur = ArrayList_GetItem(dvcman->plugin_names, i);
+ if (strcmp(cur, name) == 0)
+ {
+ plugin = ArrayList_GetItem(dvcman->plugins, i);
+ break;
+ }
+ }
+ ArrayList_Unlock(dvcman->plugin_names);
+ ArrayList_Unlock(dvcman->plugins);
+ return plugin;
+}
+
+static const ADDIN_ARGV* dvcman_get_plugin_data(IDRDYNVC_ENTRY_POINTS* pEntryPoints)
+{
+ WINPR_ASSERT(pEntryPoints);
+ return ((DVCMAN_ENTRY_POINTS*)pEntryPoints)->args;
+}
+
+static rdpContext* dvcman_get_rdp_context(IDRDYNVC_ENTRY_POINTS* pEntryPoints)
+{
+ DVCMAN_ENTRY_POINTS* entry = (DVCMAN_ENTRY_POINTS*)pEntryPoints;
+ WINPR_ASSERT(entry);
+ return entry->context;
+}
+
+static rdpSettings* dvcman_get_rdp_settings(IDRDYNVC_ENTRY_POINTS* pEntryPoints)
+{
+ rdpContext* context = dvcman_get_rdp_context(pEntryPoints);
+ WINPR_ASSERT(context);
+
+ return context->settings;
+}
+
+static UINT32 dvcman_get_channel_id(IWTSVirtualChannel* channel)
+{
+ DVCMAN_CHANNEL* dvc = (DVCMAN_CHANNEL*)channel;
+ WINPR_ASSERT(dvc);
+ return dvc->channel_id;
+}
+
+static const char* dvcman_get_channel_name(IWTSVirtualChannel* channel)
+{
+ DVCMAN_CHANNEL* dvc = (DVCMAN_CHANNEL*)channel;
+ WINPR_ASSERT(dvc);
+ return dvc->channel_name;
+}
+
+static DVCMAN_CHANNEL* dvcman_get_channel_by_id(IWTSVirtualChannelManager* pChannelMgr,
+ UINT32 ChannelId, BOOL doRef)
+{
+ DVCMAN* dvcman = (DVCMAN*)pChannelMgr;
+ DVCMAN_CHANNEL* dvcChannel = NULL;
+
+ WINPR_ASSERT(dvcman);
+ HashTable_Lock(dvcman->channelsById);
+ dvcChannel = HashTable_GetItemValue(dvcman->channelsById, &ChannelId);
+ if (dvcChannel)
+ {
+ if (doRef)
+ InterlockedIncrement(&dvcChannel->refCounter);
+ }
+
+ HashTable_Unlock(dvcman->channelsById);
+ return dvcChannel;
+}
+
+static IWTSVirtualChannel* dvcman_find_channel_by_id(IWTSVirtualChannelManager* pChannelMgr,
+ UINT32 ChannelId)
+{
+ DVCMAN_CHANNEL* channel = dvcman_get_channel_by_id(pChannelMgr, ChannelId, FALSE);
+ if (!channel)
+ return NULL;
+
+ return &channel->iface;
+}
+
+static void dvcman_plugin_terminate(void* plugin)
+{
+ IWTSPlugin* pPlugin = plugin;
+
+ WINPR_ASSERT(pPlugin);
+ UINT error = IFCALLRESULT(CHANNEL_RC_OK, pPlugin->Terminated, pPlugin);
+ if (error != CHANNEL_RC_OK)
+ WLog_ERR(TAG, "Terminated failed with error %" PRIu32 "!", error);
+}
+
+static void wts_listener_free(void* arg)
+{
+ DVCMAN_LISTENER* listener = (DVCMAN_LISTENER*)arg;
+ dvcman_wtslistener_free(listener);
+}
+
+static BOOL channelIdMatch(const void* k1, const void* k2)
+{
+ WINPR_ASSERT(k1);
+ WINPR_ASSERT(k2);
+ return *((const UINT32*)k1) == *((const UINT32*)k2);
+}
+
+static UINT32 channelIdHash(const void* id)
+{
+ WINPR_ASSERT(id);
+ return *((const UINT32*)id);
+}
+
+static void channelByIdCleanerFn(void* value)
+{
+ DVCMAN_CHANNEL* channel = (DVCMAN_CHANNEL*)value;
+ if (channel)
+ {
+ dvcman_channel_close(channel, FALSE, TRUE);
+ dvcman_channel_free(channel);
+ }
+}
+
+static IWTSVirtualChannelManager* dvcman_new(drdynvcPlugin* plugin)
+{
+ wObject* obj = NULL;
+ DVCMAN* dvcman = (DVCMAN*)calloc(1, sizeof(DVCMAN));
+
+ if (!dvcman)
+ return NULL;
+
+ dvcman->iface.CreateListener = dvcman_create_listener;
+ dvcman->iface.DestroyListener = dvcman_destroy_listener;
+ dvcman->iface.FindChannelById = dvcman_find_channel_by_id;
+ dvcman->iface.GetChannelId = dvcman_get_channel_id;
+ dvcman->iface.GetChannelName = dvcman_get_channel_name;
+ dvcman->drdynvc = plugin;
+ dvcman->channelsById = HashTable_New(TRUE);
+
+ if (!dvcman->channelsById)
+ goto fail;
+
+ HashTable_SetHashFunction(dvcman->channelsById, channelIdHash);
+ obj = HashTable_KeyObject(dvcman->channelsById);
+ WINPR_ASSERT(obj);
+ obj->fnObjectEquals = channelIdMatch;
+
+ obj = HashTable_ValueObject(dvcman->channelsById);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = channelByIdCleanerFn;
+
+ dvcman->pool = StreamPool_New(TRUE, 10);
+ if (!dvcman->pool)
+ goto fail;
+
+ dvcman->listeners = HashTable_New(TRUE);
+ if (!dvcman->listeners)
+ goto fail;
+ HashTable_SetHashFunction(dvcman->listeners, HashTable_StringHash);
+
+ obj = HashTable_KeyObject(dvcman->listeners);
+ obj->fnObjectEquals = HashTable_StringCompare;
+
+ obj = HashTable_ValueObject(dvcman->listeners);
+ obj->fnObjectFree = wts_listener_free;
+
+ dvcman->plugin_names = ArrayList_New(TRUE);
+ if (!dvcman->plugin_names)
+ goto fail;
+ obj = ArrayList_Object(dvcman->plugin_names);
+ obj->fnObjectNew = winpr_ObjectStringClone;
+ obj->fnObjectFree = winpr_ObjectStringFree;
+
+ dvcman->plugins = ArrayList_New(TRUE);
+ if (!dvcman->plugins)
+ goto fail;
+ obj = ArrayList_Object(dvcman->plugins);
+ obj->fnObjectFree = dvcman_plugin_terminate;
+ return &dvcman->iface;
+fail:
+ dvcman_free(plugin, &dvcman->iface);
+ return NULL;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT dvcman_load_addin(drdynvcPlugin* drdynvc, IWTSVirtualChannelManager* pChannelMgr,
+ const ADDIN_ARGV* args, rdpContext* context)
+{
+ PDVC_PLUGIN_ENTRY pDVCPluginEntry = NULL;
+
+ WINPR_ASSERT(drdynvc);
+ WINPR_ASSERT(pChannelMgr);
+ WINPR_ASSERT(args);
+ WINPR_ASSERT(context);
+
+ WLog_Print(drdynvc->log, WLOG_INFO, "Loading Dynamic Virtual Channel %s", args->argv[0]);
+ pDVCPluginEntry = (PDVC_PLUGIN_ENTRY)freerdp_load_channel_addin_entry(
+ args->argv[0], NULL, NULL, FREERDP_ADDIN_CHANNEL_DYNAMIC);
+
+ if (pDVCPluginEntry)
+ {
+ DVCMAN_ENTRY_POINTS entryPoints = { 0 };
+
+ entryPoints.iface.RegisterPlugin = dvcman_register_plugin;
+ entryPoints.iface.GetPlugin = dvcman_get_plugin;
+ entryPoints.iface.GetPluginData = dvcman_get_plugin_data;
+ entryPoints.iface.GetRdpSettings = dvcman_get_rdp_settings;
+ entryPoints.iface.GetRdpContext = dvcman_get_rdp_context;
+ entryPoints.dvcman = (DVCMAN*)pChannelMgr;
+ entryPoints.args = args;
+ entryPoints.context = context;
+ return pDVCPluginEntry(&entryPoints.iface);
+ }
+
+ return ERROR_INVALID_FUNCTION;
+}
+
+static void dvcman_channel_free(DVCMAN_CHANNEL* channel)
+{
+ if (!channel)
+ return;
+
+ if (channel->dvc_data)
+ Stream_Release(channel->dvc_data);
+
+ DeleteCriticalSection(&(channel->lock));
+ free(channel->channel_name);
+ free(channel);
+}
+
+static void dvcman_channel_unref(DVCMAN_CHANNEL* channel)
+{
+ DVCMAN* dvcman = NULL;
+
+ WINPR_ASSERT(channel);
+ if (InterlockedDecrement(&channel->refCounter))
+ return;
+
+ dvcman = channel->dvcman;
+ HashTable_Remove(dvcman->channelsById, &channel->channel_id);
+}
+
+static UINT dvcchannel_send_close(DVCMAN_CHANNEL* channel)
+{
+ WINPR_ASSERT(channel);
+ DVCMAN* dvcman = channel->dvcman;
+ drdynvcPlugin* drdynvc = dvcman->drdynvc;
+ wStream* s = StreamPool_Take(dvcman->pool, 5);
+
+ if (!s)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "StreamPool_Take failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT8(s, (CLOSE_REQUEST_PDU << 4) | 0x02);
+ Stream_Write_UINT32(s, channel->channel_id);
+ return drdynvc_send(drdynvc, s);
+}
+
+static void check_open_close_receive(DVCMAN_CHANNEL* channel)
+{
+ WINPR_ASSERT(channel);
+
+ IWTSVirtualChannelCallback* cb = channel->channel_callback;
+ const char* name = channel->channel_name;
+ const UINT32 id = channel->channel_id;
+
+ WINPR_ASSERT(cb);
+ if (cb->OnOpen || cb->OnClose)
+ {
+ if (!cb->OnOpen || !cb->OnClose)
+ WLog_WARN(TAG, "{%s:%" PRIu32 "} OnOpen=%p, OnClose=%p", name, id, cb->OnOpen,
+ cb->OnClose);
+ }
+}
+
+static UINT dvcman_call_on_receive(DVCMAN_CHANNEL* channel, wStream* data)
+{
+ WINPR_ASSERT(channel);
+ WINPR_ASSERT(data);
+
+ IWTSVirtualChannelCallback* cb = channel->channel_callback;
+ WINPR_ASSERT(cb);
+
+ check_open_close_receive(channel);
+ WINPR_ASSERT(cb->OnDataReceived);
+ return cb->OnDataReceived(cb, data);
+}
+
+static UINT dvcman_channel_close(DVCMAN_CHANNEL* channel, BOOL perRequest, BOOL fromHashTableFn)
+{
+ UINT error = CHANNEL_RC_OK;
+ drdynvcPlugin* drdynvc = NULL;
+ DrdynvcClientContext* context = NULL;
+
+ WINPR_ASSERT(channel);
+ switch (channel->state)
+ {
+ case DVC_CHANNEL_INIT:
+ break;
+ case DVC_CHANNEL_RUNNING:
+ drdynvc = channel->dvcman->drdynvc;
+ context = drdynvc->context;
+ if (perRequest)
+ WLog_Print(drdynvc->log, WLOG_DEBUG, "sending close confirm for '%s'",
+ channel->channel_name);
+
+ error = dvcchannel_send_close(channel);
+ if (error != CHANNEL_RC_OK)
+ {
+ const char* msg = "error when sending close confirm for '%s'";
+ if (perRequest)
+ msg = "error when sending closeRequest for '%s'";
+
+ WLog_Print(drdynvc->log, WLOG_DEBUG, msg, channel->channel_name);
+ }
+
+ channel->state = DVC_CHANNEL_CLOSED;
+
+ IWTSVirtualChannelCallback* cb = channel->channel_callback;
+ if (cb)
+ {
+ check_open_close_receive(channel);
+ IFCALL(cb->OnClose, cb);
+ }
+
+ channel->channel_callback = NULL;
+
+ if (channel->dvcman && channel->dvcman->drdynvc)
+ {
+ if (context)
+ {
+ IFCALLRET(context->OnChannelDisconnected, error, context, channel->channel_name,
+ channel->pInterface);
+ }
+ }
+
+ if (!fromHashTableFn)
+ dvcman_channel_unref(channel);
+ break;
+ case DVC_CHANNEL_CLOSED:
+ break;
+ }
+
+ return error;
+}
+
+static DVCMAN_CHANNEL* dvcman_channel_new(drdynvcPlugin* drdynvc,
+ IWTSVirtualChannelManager* pChannelMgr, UINT32 ChannelId,
+ const char* ChannelName)
+{
+ DVCMAN_CHANNEL* channel = NULL;
+
+ WINPR_ASSERT(drdynvc);
+ WINPR_ASSERT(pChannelMgr);
+ channel = (DVCMAN_CHANNEL*)calloc(1, sizeof(DVCMAN_CHANNEL));
+
+ if (!channel)
+ return NULL;
+
+ channel->dvcman = (DVCMAN*)pChannelMgr;
+ channel->channel_id = ChannelId;
+ channel->refCounter = 1;
+ channel->state = DVC_CHANNEL_INIT;
+ channel->channel_name = _strdup(ChannelName);
+
+ if (!channel->channel_name)
+ goto fail;
+
+ if (!InitializeCriticalSectionEx(&(channel->lock), 0, 0))
+ goto fail;
+
+ return channel;
+fail:
+ dvcman_channel_free(channel);
+ return NULL;
+}
+
+static void dvcman_clear(drdynvcPlugin* drdynvc, IWTSVirtualChannelManager* pChannelMgr)
+{
+ DVCMAN* dvcman = (DVCMAN*)pChannelMgr;
+
+ WINPR_ASSERT(dvcman);
+ WINPR_UNUSED(drdynvc);
+
+ HashTable_Clear(dvcman->channelsById);
+ ArrayList_Clear(dvcman->plugins);
+ ArrayList_Clear(dvcman->plugin_names);
+ HashTable_Clear(dvcman->listeners);
+}
+static void dvcman_free(drdynvcPlugin* drdynvc, IWTSVirtualChannelManager* pChannelMgr)
+{
+ DVCMAN* dvcman = (DVCMAN*)pChannelMgr;
+
+ WINPR_ASSERT(dvcman);
+ WINPR_UNUSED(drdynvc);
+
+ HashTable_Free(dvcman->channelsById);
+ ArrayList_Free(dvcman->plugins);
+ ArrayList_Free(dvcman->plugin_names);
+ HashTable_Free(dvcman->listeners);
+
+ StreamPool_Free(dvcman->pool);
+ free(dvcman);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT dvcman_init(drdynvcPlugin* drdynvc, IWTSVirtualChannelManager* pChannelMgr)
+{
+ DVCMAN* dvcman = (DVCMAN*)pChannelMgr;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(dvcman);
+ ArrayList_Lock(dvcman->plugins);
+ for (size_t i = 0; i < ArrayList_Count(dvcman->plugins); i++)
+ {
+ IWTSPlugin* pPlugin = ArrayList_GetItem(dvcman->plugins, i);
+
+ error = IFCALLRESULT(CHANNEL_RC_OK, pPlugin->Initialize, pPlugin, pChannelMgr);
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "Initialize failed with error %" PRIu32 "!",
+ error);
+ goto fail;
+ }
+ }
+
+fail:
+ ArrayList_Unlock(dvcman->plugins);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT dvcman_write_channel(IWTSVirtualChannel* pChannel, ULONG cbSize, const BYTE* pBuffer,
+ void* pReserved)
+{
+ BOOL close = FALSE;
+ UINT status = 0;
+ DVCMAN_CHANNEL* channel = (DVCMAN_CHANNEL*)pChannel;
+
+ WINPR_UNUSED(pReserved);
+ if (!channel || !channel->dvcman)
+ return CHANNEL_RC_BAD_CHANNEL;
+
+ EnterCriticalSection(&(channel->lock));
+ status =
+ drdynvc_write_data(channel->dvcman->drdynvc, channel->channel_id, pBuffer, cbSize, &close);
+ LeaveCriticalSection(&(channel->lock));
+ /* Close delayed, it removes the channel struct */
+ if (close)
+ dvcman_channel_close(channel, FALSE, FALSE);
+
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT dvcman_close_channel_iface(IWTSVirtualChannel* pChannel)
+{
+ DVCMAN_CHANNEL* channel = (DVCMAN_CHANNEL*)pChannel;
+
+ if (!channel)
+ return CHANNEL_RC_BAD_CHANNEL;
+
+ WLog_DBG(TAG, "close_channel_iface: id=%" PRIu32 "", channel->channel_id);
+ return dvcman_channel_close(channel, FALSE, FALSE);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static DVCMAN_CHANNEL* dvcman_create_channel(drdynvcPlugin* drdynvc,
+ IWTSVirtualChannelManager* pChannelMgr,
+ UINT32 ChannelId, const char* ChannelName, UINT* res)
+{
+ BOOL bAccept = 0;
+ DVCMAN_CHANNEL* channel = NULL;
+ DrdynvcClientContext* context = NULL;
+ DVCMAN* dvcman = (DVCMAN*)pChannelMgr;
+ DVCMAN_LISTENER* listener = NULL;
+ IWTSVirtualChannelCallback* pCallback = NULL;
+
+ WINPR_ASSERT(dvcman);
+ WINPR_ASSERT(res);
+
+ HashTable_Lock(dvcman->listeners);
+ listener = (DVCMAN_LISTENER*)HashTable_GetItemValue(dvcman->listeners, ChannelName);
+ if (!listener)
+ {
+ *res = ERROR_NOT_FOUND;
+ goto out;
+ }
+
+ channel = dvcman_get_channel_by_id(pChannelMgr, ChannelId, FALSE);
+ if (channel)
+ {
+ switch (channel->state)
+ {
+ case DVC_CHANNEL_RUNNING:
+ WLog_Print(drdynvc->log, WLOG_ERROR,
+ "Protocol error: Duplicated ChannelId %" PRIu32 " (%s)!", ChannelId,
+ ChannelName);
+ *res = CHANNEL_RC_ALREADY_OPEN;
+ goto out;
+
+ case DVC_CHANNEL_CLOSED:
+ case DVC_CHANNEL_INIT:
+ default:
+ WLog_Print(drdynvc->log, WLOG_ERROR, "not expecting a createChannel from state %d",
+ channel->state);
+ *res = CHANNEL_RC_INITIALIZATION_ERROR;
+ goto out;
+ }
+ }
+ else
+ {
+ if (!(channel = dvcman_channel_new(drdynvc, pChannelMgr, ChannelId, ChannelName)))
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "dvcman_channel_new failed!");
+ *res = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+ }
+
+ if (!HashTable_Insert(dvcman->channelsById, &channel->channel_id, channel))
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "unable to register channel in our channel list");
+ *res = ERROR_INTERNAL_ERROR;
+ dvcman_channel_free(channel);
+ channel = NULL;
+ goto out;
+ }
+
+ channel->iface.Write = dvcman_write_channel;
+ channel->iface.Close = dvcman_close_channel_iface;
+ bAccept = TRUE;
+
+ *res = listener->listener_callback->OnNewChannelConnection(
+ listener->listener_callback, &channel->iface, NULL, &bAccept, &pCallback);
+
+ if (*res != CHANNEL_RC_OK)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR,
+ "OnNewChannelConnection failed with error %" PRIu32 "!", *res);
+ *res = ERROR_INTERNAL_ERROR;
+ dvcman_channel_unref(channel);
+ goto out;
+ }
+
+ if (!bAccept)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "OnNewChannelConnection returned with bAccept FALSE!");
+ *res = ERROR_INTERNAL_ERROR;
+ dvcman_channel_unref(channel);
+ channel = NULL;
+ goto out;
+ }
+
+ WLog_Print(drdynvc->log, WLOG_DEBUG, "listener %s created new channel %" PRIu32 "",
+ listener->channel_name, channel->channel_id);
+ channel->state = DVC_CHANNEL_RUNNING;
+ channel->channel_callback = pCallback;
+ channel->pInterface = listener->iface.pInterface;
+ context = dvcman->drdynvc->context;
+
+ IFCALLRET(context->OnChannelConnected, *res, context, ChannelName, listener->iface.pInterface);
+ if (*res != CHANNEL_RC_OK)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR,
+ "context.OnChannelConnected failed with error %" PRIu32 "", *res);
+ }
+
+out:
+ HashTable_Unlock(dvcman->listeners);
+
+ return channel;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT dvcman_open_channel(drdynvcPlugin* drdynvc, DVCMAN_CHANNEL* channel)
+{
+ IWTSVirtualChannelCallback* pCallback = NULL;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(drdynvc);
+ WINPR_ASSERT(channel);
+ if (channel->state == DVC_CHANNEL_RUNNING)
+ {
+ pCallback = channel->channel_callback;
+
+ if (pCallback->OnOpen)
+ {
+ check_open_close_receive(channel);
+ error = pCallback->OnOpen(pCallback);
+ if (error)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "OnOpen failed with error %" PRIu32 "!",
+ error);
+ goto out;
+ }
+ }
+
+ WLog_Print(drdynvc->log, WLOG_DEBUG, "open_channel: ChannelId %" PRIu32 "",
+ channel->channel_id);
+ }
+
+out:
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT dvcman_receive_channel_data_first(DVCMAN_CHANNEL* channel, UINT32 length)
+{
+ WINPR_ASSERT(channel);
+ WINPR_ASSERT(channel->dvcman);
+ if (channel->dvc_data)
+ Stream_Release(channel->dvc_data);
+
+ channel->dvc_data = StreamPool_Take(channel->dvcman->pool, length);
+
+ if (!channel->dvc_data)
+ {
+ drdynvcPlugin* drdynvc = channel->dvcman->drdynvc;
+ WLog_Print(drdynvc->log, WLOG_ERROR, "StreamPool_Take failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ channel->dvc_data_length = length;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT dvcman_receive_channel_data(DVCMAN_CHANNEL* channel, wStream* data,
+ UINT32 ThreadingFlags)
+{
+ UINT status = CHANNEL_RC_OK;
+ size_t dataSize = Stream_GetRemainingLength(data);
+
+ WINPR_ASSERT(channel);
+ WINPR_ASSERT(channel->dvcman);
+ if (channel->dvc_data)
+ {
+ drdynvcPlugin* drdynvc = channel->dvcman->drdynvc;
+
+ /* Fragmented data */
+ if (Stream_GetPosition(channel->dvc_data) + dataSize > channel->dvc_data_length)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "data exceeding declared length!");
+ Stream_Release(channel->dvc_data);
+ channel->dvc_data = NULL;
+ status = ERROR_INVALID_DATA;
+ goto out;
+ }
+
+ Stream_Copy(data, channel->dvc_data, dataSize);
+
+ if (Stream_GetPosition(channel->dvc_data) >= channel->dvc_data_length)
+ {
+ Stream_SealLength(channel->dvc_data);
+ Stream_SetPosition(channel->dvc_data, 0);
+
+ status = dvcman_call_on_receive(channel, channel->dvc_data);
+ Stream_Release(channel->dvc_data);
+ channel->dvc_data = NULL;
+ }
+ }
+ else
+ status = dvcman_call_on_receive(channel, data);
+
+out:
+ return status;
+}
+
+static UINT8 drdynvc_write_variable_uint(wStream* s, UINT32 val)
+{
+ UINT8 cb = 0;
+
+ if (val <= 0xFF)
+ {
+ cb = 0;
+ Stream_Write_UINT8(s, (UINT8)val);
+ }
+ else if (val <= 0xFFFF)
+ {
+ cb = 1;
+ Stream_Write_UINT16(s, (UINT16)val);
+ }
+ else
+ {
+ cb = 2;
+ Stream_Write_UINT32(s, val);
+ }
+
+ return cb;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drdynvc_send(drdynvcPlugin* drdynvc, wStream* s)
+{
+ UINT status = 0;
+
+ if (!drdynvc)
+ status = CHANNEL_RC_BAD_CHANNEL_HANDLE;
+ else
+ {
+ WINPR_ASSERT(drdynvc->channelEntryPoints.pVirtualChannelWriteEx);
+ status = drdynvc->channelEntryPoints.pVirtualChannelWriteEx(
+ drdynvc->InitHandle, drdynvc->OpenHandle, Stream_Buffer(s),
+ (UINT32)Stream_GetPosition(s), s);
+ }
+
+ switch (status)
+ {
+ case CHANNEL_RC_OK:
+ return CHANNEL_RC_OK;
+
+ case CHANNEL_RC_NOT_CONNECTED:
+ Stream_Release(s);
+ return CHANNEL_RC_OK;
+
+ case CHANNEL_RC_BAD_CHANNEL_HANDLE:
+ Stream_Release(s);
+ WLog_ERR(TAG, "VirtualChannelWriteEx failed with CHANNEL_RC_BAD_CHANNEL_HANDLE");
+ return status;
+
+ default:
+ Stream_Release(s);
+ WLog_Print(drdynvc->log, WLOG_ERROR,
+ "VirtualChannelWriteEx failed with %s [%08" PRIX32 "]",
+ WTSErrorToString(status), status);
+ return status;
+ }
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drdynvc_write_data(drdynvcPlugin* drdynvc, UINT32 ChannelId, const BYTE* data,
+ UINT32 dataSize, BOOL* close)
+{
+ wStream* data_out = NULL;
+ size_t pos = 0;
+ UINT8 cbChId = 0;
+ UINT8 cbLen = 0;
+ unsigned long chunkLength = 0;
+ UINT status = CHANNEL_RC_BAD_INIT_HANDLE;
+ DVCMAN* dvcman = NULL;
+
+ if (!drdynvc)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ dvcman = (DVCMAN*)drdynvc->channel_mgr;
+ WINPR_ASSERT(dvcman);
+
+ WLog_Print(drdynvc->log, WLOG_TRACE, "write_data: ChannelId=%" PRIu32 " size=%" PRIu32 "",
+ ChannelId, dataSize);
+ data_out = StreamPool_Take(dvcman->pool, CHANNEL_CHUNK_LENGTH);
+
+ if (!data_out)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "StreamPool_Take failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_SetPosition(data_out, 1);
+ cbChId = drdynvc_write_variable_uint(data_out, ChannelId);
+ pos = Stream_GetPosition(data_out);
+
+ if (dataSize == 0)
+ {
+ /* TODO: shall treat that case with write(0) that do a close */
+ *close = TRUE;
+ Stream_Release(data_out);
+ }
+ else if (dataSize <= CHANNEL_CHUNK_LENGTH - pos)
+ {
+ Stream_SetPosition(data_out, 0);
+ Stream_Write_UINT8(data_out, (DATA_PDU << 4) | cbChId);
+ Stream_SetPosition(data_out, pos);
+ Stream_Write(data_out, data, dataSize);
+ status = drdynvc_send(drdynvc, data_out);
+ }
+ else
+ {
+ /* Fragment the data */
+ cbLen = drdynvc_write_variable_uint(data_out, dataSize);
+ pos = Stream_GetPosition(data_out);
+ Stream_SetPosition(data_out, 0);
+ Stream_Write_UINT8(data_out, (DATA_FIRST_PDU << 4) | cbChId | (cbLen << 2));
+ Stream_SetPosition(data_out, pos);
+ chunkLength = CHANNEL_CHUNK_LENGTH - pos;
+ Stream_Write(data_out, data, chunkLength);
+ data += chunkLength;
+ dataSize -= chunkLength;
+ status = drdynvc_send(drdynvc, data_out);
+
+ while (status == CHANNEL_RC_OK && dataSize > 0)
+ {
+ data_out = StreamPool_Take(dvcman->pool, CHANNEL_CHUNK_LENGTH);
+
+ if (!data_out)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "StreamPool_Take failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_SetPosition(data_out, 1);
+ cbChId = drdynvc_write_variable_uint(data_out, ChannelId);
+ pos = Stream_GetPosition(data_out);
+ Stream_SetPosition(data_out, 0);
+ Stream_Write_UINT8(data_out, (DATA_PDU << 4) | cbChId);
+ Stream_SetPosition(data_out, pos);
+ chunkLength = dataSize;
+
+ if (chunkLength > CHANNEL_CHUNK_LENGTH - pos)
+ chunkLength = CHANNEL_CHUNK_LENGTH - pos;
+
+ Stream_Write(data_out, data, chunkLength);
+ data += chunkLength;
+ dataSize -= chunkLength;
+ status = drdynvc_send(drdynvc, data_out);
+ }
+ }
+
+ if (status != CHANNEL_RC_OK)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "VirtualChannelWriteEx failed with %s [%08" PRIX32 "]",
+ WTSErrorToString(status), status);
+ return status;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drdynvc_send_capability_response(drdynvcPlugin* drdynvc)
+{
+ UINT status = 0;
+ wStream* s = NULL;
+ DVCMAN* dvcman = NULL;
+
+ if (!drdynvc)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ dvcman = (DVCMAN*)drdynvc->channel_mgr;
+ WINPR_ASSERT(dvcman);
+
+ WLog_Print(drdynvc->log, WLOG_TRACE, "capability_response");
+ s = StreamPool_Take(dvcman->pool, 4);
+
+ if (!s)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "Stream_Ndrdynvc_write_variable_uintew failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, 0x0050); /* Cmd+Sp+cbChId+Pad. Note: MSTSC sends 0x005c */
+ Stream_Write_UINT16(s, drdynvc->version);
+ status = drdynvc_send(drdynvc, s);
+
+ if (status != CHANNEL_RC_OK)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "VirtualChannelWriteEx failed with %s [%08" PRIX32 "]",
+ WTSErrorToString(status), status);
+ }
+
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drdynvc_process_capability_request(drdynvcPlugin* drdynvc, int Sp, int cbChId,
+ wStream* s)
+{
+ UINT status = 0;
+
+ if (!drdynvc)
+ return CHANNEL_RC_BAD_INIT_HANDLE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 3))
+ return ERROR_INVALID_DATA;
+
+ WLog_Print(drdynvc->log, WLOG_TRACE, "capability_request Sp=%d cbChId=%d", Sp, cbChId);
+ Stream_Seek(s, 1); /* pad */
+ Stream_Read_UINT16(s, drdynvc->version);
+
+ /* RDP8 servers offer version 3, though Microsoft forgot to document it
+ * in their early documents. It behaves the same as version 2.
+ */
+ if ((drdynvc->version == 2) || (drdynvc->version == 3))
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, drdynvc->PriorityCharge0);
+ Stream_Read_UINT16(s, drdynvc->PriorityCharge1);
+ Stream_Read_UINT16(s, drdynvc->PriorityCharge2);
+ Stream_Read_UINT16(s, drdynvc->PriorityCharge3);
+ }
+
+ status = drdynvc_send_capability_response(drdynvc);
+ drdynvc->state = DRDYNVC_STATE_READY;
+ return status;
+}
+
+static UINT32 drdynvc_cblen_to_bytes(int cbLen)
+{
+ switch (cbLen)
+ {
+ case 0:
+ return 1;
+
+ case 1:
+ return 2;
+
+ default:
+ return 4;
+ }
+}
+
+static UINT32 drdynvc_read_variable_uint(wStream* s, int cbLen)
+{
+ UINT32 val = 0;
+
+ switch (cbLen)
+ {
+ case 0:
+ Stream_Read_UINT8(s, val);
+ break;
+
+ case 1:
+ Stream_Read_UINT16(s, val);
+ break;
+
+ default:
+ Stream_Read_UINT32(s, val);
+ break;
+ }
+
+ return val;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drdynvc_process_create_request(drdynvcPlugin* drdynvc, int Sp, int cbChId, wStream* s)
+{
+ UINT status = 0;
+ wStream* data_out = NULL;
+ UINT channel_status = 0;
+ DVCMAN* dvcman = NULL;
+ DVCMAN_CHANNEL* channel = NULL;
+ UINT32 retStatus = 0;
+
+ WINPR_UNUSED(Sp);
+ if (!drdynvc)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ dvcman = (DVCMAN*)drdynvc->channel_mgr;
+ WINPR_ASSERT(dvcman);
+
+ if (drdynvc->state == DRDYNVC_STATE_CAPABILITIES)
+ {
+ /**
+ * For some reason the server does not always send the
+ * capabilities pdu as it should. When this happens,
+ * send a capabilities response.
+ */
+ drdynvc->version = 3;
+
+ if ((status = drdynvc_send_capability_response(drdynvc)))
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "drdynvc_send_capability_response failed!");
+ return status;
+ }
+
+ drdynvc->state = DRDYNVC_STATE_READY;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, drdynvc_cblen_to_bytes(cbChId)))
+ return ERROR_INVALID_DATA;
+
+ const UINT32 ChannelId = drdynvc_read_variable_uint(s, cbChId);
+ const size_t pos = Stream_GetPosition(s);
+ const char* name = Stream_ConstPointer(s);
+ const size_t length = Stream_GetRemainingLength(s);
+
+ if (strnlen(name, length) >= length)
+ return ERROR_INVALID_DATA;
+
+ WLog_Print(drdynvc->log, WLOG_DEBUG,
+ "process_create_request: ChannelId=%" PRIu32 " ChannelName=%s", ChannelId, name);
+
+ data_out = StreamPool_Take(dvcman->pool, pos + 4);
+ if (!data_out)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "StreamPool_Take failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT8(data_out, (CREATE_REQUEST_PDU << 4) | cbChId);
+ Stream_SetPosition(s, 1);
+ Stream_Copy(s, data_out, pos - 1);
+
+ channel =
+ dvcman_create_channel(drdynvc, drdynvc->channel_mgr, ChannelId, name, &channel_status);
+ switch (channel_status)
+ {
+ case CHANNEL_RC_OK:
+ WLog_Print(drdynvc->log, WLOG_DEBUG, "channel created");
+ retStatus = 0;
+ break;
+ case CHANNEL_RC_NO_MEMORY:
+ WLog_Print(drdynvc->log, WLOG_DEBUG, "not enough memory for channel creation");
+ retStatus = STATUS_NO_MEMORY;
+ break;
+ case ERROR_NOT_FOUND:
+ WLog_Print(drdynvc->log, WLOG_DEBUG, "no listener for '%s'", name);
+ retStatus = (UINT32)0xC0000001; /* same code used by mstsc, STATUS_UNSUCCESSFUL */
+ break;
+ default:
+ WLog_Print(drdynvc->log, WLOG_DEBUG, "channel creation error");
+ retStatus = (UINT32)0xC0000001; /* same code used by mstsc, STATUS_UNSUCCESSFUL */
+ break;
+ }
+ Stream_Write_UINT32(data_out, retStatus);
+
+ status = drdynvc_send(drdynvc, data_out);
+ if (status != CHANNEL_RC_OK)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "VirtualChannelWriteEx failed with %s [%08" PRIX32 "]",
+ WTSErrorToString(status), status);
+ dvcman_channel_unref(channel);
+ return status;
+ }
+
+ if (channel_status == CHANNEL_RC_OK)
+ {
+ if ((status = dvcman_open_channel(drdynvc, channel)))
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR,
+ "dvcman_open_channel failed with error %" PRIu32 "!", status);
+ return status;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drdynvc_process_data_first(drdynvcPlugin* drdynvc, int Sp, int cbChId, wStream* s,
+ UINT32 ThreadingFlags)
+{
+ UINT status = CHANNEL_RC_OK;
+ UINT32 Length = 0;
+ UINT32 ChannelId = 0;
+ DVCMAN_CHANNEL* channel = NULL;
+
+ WINPR_ASSERT(drdynvc);
+ if (!Stream_CheckAndLogRequiredLength(
+ TAG, s, drdynvc_cblen_to_bytes(cbChId) + drdynvc_cblen_to_bytes(Sp)))
+ return ERROR_INVALID_DATA;
+
+ ChannelId = drdynvc_read_variable_uint(s, cbChId);
+ Length = drdynvc_read_variable_uint(s, Sp);
+ WLog_Print(drdynvc->log, WLOG_TRACE,
+ "process_data_first: Sp=%d cbChId=%d, ChannelId=%" PRIu32 " Length=%" PRIu32 "", Sp,
+ cbChId, ChannelId, Length);
+
+ channel = dvcman_get_channel_by_id(drdynvc->channel_mgr, ChannelId, TRUE);
+ if (!channel)
+ {
+ /**
+ * Windows Server 2012 R2 can send some messages over
+ * Microsoft::Windows::RDS::Geometry::v08.01 even if the dynamic virtual channel wasn't
+ * registered on our side. Ignoring it works.
+ */
+ WLog_Print(drdynvc->log, WLOG_ERROR, "ChannelId %" PRIu32 " not found!", ChannelId);
+ return CHANNEL_RC_OK;
+ }
+
+ if (channel->state != DVC_CHANNEL_RUNNING)
+ goto out;
+
+ status = dvcman_receive_channel_data_first(channel, Length);
+
+ if (status == CHANNEL_RC_OK)
+ status = dvcman_receive_channel_data(channel, s, ThreadingFlags);
+
+ if (status != CHANNEL_RC_OK)
+ status = dvcman_channel_close(channel, FALSE, FALSE);
+
+out:
+ dvcman_channel_unref(channel);
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drdynvc_process_data(drdynvcPlugin* drdynvc, int Sp, int cbChId, wStream* s,
+ UINT32 ThreadingFlags)
+{
+ UINT32 ChannelId = 0;
+ DVCMAN_CHANNEL* channel = NULL;
+ UINT status = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(drdynvc);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, drdynvc_cblen_to_bytes(cbChId)))
+ return ERROR_INVALID_DATA;
+
+ ChannelId = drdynvc_read_variable_uint(s, cbChId);
+ WLog_Print(drdynvc->log, WLOG_TRACE, "process_data: Sp=%d cbChId=%d, ChannelId=%" PRIu32 "", Sp,
+ cbChId, ChannelId);
+
+ channel = dvcman_get_channel_by_id(drdynvc->channel_mgr, ChannelId, TRUE);
+ if (!channel)
+ {
+ /**
+ * Windows Server 2012 R2 can send some messages over
+ * Microsoft::Windows::RDS::Geometry::v08.01 even if the dynamic virtual channel wasn't
+ * registered on our side. Ignoring it works.
+ */
+ WLog_Print(drdynvc->log, WLOG_ERROR, "ChannelId %" PRIu32 " not found!", ChannelId);
+ return CHANNEL_RC_OK;
+ }
+
+ if (channel->state != DVC_CHANNEL_RUNNING)
+ goto out;
+
+ status = dvcman_receive_channel_data(channel, s, ThreadingFlags);
+ if (status != CHANNEL_RC_OK)
+ status = dvcman_channel_close(channel, FALSE, FALSE);
+
+out:
+ dvcman_channel_unref(channel);
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drdynvc_process_close_request(drdynvcPlugin* drdynvc, int Sp, int cbChId, wStream* s)
+{
+ UINT32 ChannelId = 0;
+ DVCMAN_CHANNEL* channel = NULL;
+
+ WINPR_ASSERT(drdynvc);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, drdynvc_cblen_to_bytes(cbChId)))
+ return ERROR_INVALID_DATA;
+
+ ChannelId = drdynvc_read_variable_uint(s, cbChId);
+ WLog_Print(drdynvc->log, WLOG_DEBUG,
+ "process_close_request: Sp=%d cbChId=%d, ChannelId=%" PRIu32 "", Sp, cbChId,
+ ChannelId);
+
+ channel = (DVCMAN_CHANNEL*)dvcman_get_channel_by_id(drdynvc->channel_mgr, ChannelId, TRUE);
+ if (!channel)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "dvcman_close_request channel %" PRIu32 " not present",
+ ChannelId);
+ return CHANNEL_RC_OK;
+ }
+
+ dvcman_channel_close(channel, TRUE, FALSE);
+ dvcman_channel_unref(channel);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drdynvc_order_recv(drdynvcPlugin* drdynvc, wStream* s, UINT32 ThreadingFlags)
+{
+ UINT8 value = 0;
+
+ WINPR_ASSERT(drdynvc);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT8(s, value);
+ const UINT8 Cmd = (value & 0xf0) >> 4;
+ const UINT8 Sp = (value & 0x0c) >> 2;
+ const UINT8 cbChId = (value & 0x03) >> 0;
+ WLog_Print(drdynvc->log, WLOG_TRACE, "order_recv: Cmd=%s, Sp=%" PRIu8 " cbChId=%" PRIu8,
+ drdynvc_get_packet_type(Cmd), Sp, cbChId);
+
+ switch (Cmd)
+ {
+ case CAPABILITY_REQUEST_PDU:
+ return drdynvc_process_capability_request(drdynvc, Sp, cbChId, s);
+
+ case CREATE_REQUEST_PDU:
+ return drdynvc_process_create_request(drdynvc, Sp, cbChId, s);
+
+ case DATA_FIRST_PDU:
+ return drdynvc_process_data_first(drdynvc, Sp, cbChId, s, ThreadingFlags);
+
+ case DATA_PDU:
+ return drdynvc_process_data(drdynvc, Sp, cbChId, s, ThreadingFlags);
+
+ case CLOSE_REQUEST_PDU:
+ return drdynvc_process_close_request(drdynvc, Sp, cbChId, s);
+
+ default:
+ WLog_Print(drdynvc->log, WLOG_ERROR, "unknown drdynvc cmd 0x%x", Cmd);
+ return ERROR_INTERNAL_ERROR;
+ }
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drdynvc_virtual_channel_event_data_received(drdynvcPlugin* drdynvc, void* pData,
+ UINT32 dataLength, UINT32 totalLength,
+ UINT32 dataFlags)
+{
+ wStream* data_in = NULL;
+
+ WINPR_ASSERT(drdynvc);
+ if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME))
+ {
+ return CHANNEL_RC_OK;
+ }
+
+ if (dataFlags & CHANNEL_FLAG_FIRST)
+ {
+ DVCMAN* mgr = (DVCMAN*)drdynvc->channel_mgr;
+ if (drdynvc->data_in)
+ Stream_Release(drdynvc->data_in);
+
+ drdynvc->data_in = StreamPool_Take(mgr->pool, totalLength);
+ }
+
+ if (!(data_in = drdynvc->data_in))
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "StreamPool_Take failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(data_in, dataLength))
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!");
+ Stream_Release(drdynvc->data_in);
+ drdynvc->data_in = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Write(data_in, pData, dataLength);
+
+ if (dataFlags & CHANNEL_FLAG_LAST)
+ {
+ const size_t cap = Stream_Capacity(data_in);
+ const size_t pos = Stream_GetPosition(data_in);
+ if (cap < pos)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "drdynvc_plugin_process_received: read error");
+ return ERROR_INVALID_DATA;
+ }
+
+ drdynvc->data_in = NULL;
+ Stream_SealLength(data_in);
+ Stream_SetPosition(data_in, 0);
+
+ if (drdynvc->async)
+ {
+ if (!MessageQueue_Post(drdynvc->queue, NULL, 0, (void*)data_in, NULL))
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "MessageQueue_Post failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+ else
+ {
+ UINT error = drdynvc_order_recv(drdynvc, data_in, TRUE);
+ Stream_Release(data_in);
+
+ if (error)
+ {
+ WLog_Print(drdynvc->log, WLOG_WARN,
+ "drdynvc_order_recv failed with error %" PRIu32 "!", error);
+ }
+ }
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static void VCAPITYPE drdynvc_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle,
+ UINT event, LPVOID pData,
+ UINT32 dataLength, UINT32 totalLength,
+ UINT32 dataFlags)
+{
+ UINT error = CHANNEL_RC_OK;
+ drdynvcPlugin* drdynvc = (drdynvcPlugin*)lpUserParam;
+
+ WINPR_ASSERT(drdynvc);
+ switch (event)
+ {
+ case CHANNEL_EVENT_DATA_RECEIVED:
+ if (!drdynvc || (drdynvc->OpenHandle != openHandle))
+ {
+ WLog_ERR(TAG, "drdynvc_virtual_channel_open_event: error no match");
+ return;
+ }
+ if ((error = drdynvc_virtual_channel_event_data_received(drdynvc, pData, dataLength,
+ totalLength, dataFlags)))
+ WLog_Print(drdynvc->log, WLOG_ERROR,
+ "drdynvc_virtual_channel_event_data_received failed with error %" PRIu32
+ "",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_WRITE_CANCELLED:
+ case CHANNEL_EVENT_WRITE_COMPLETE:
+ {
+ wStream* s = (wStream*)pData;
+ Stream_Release(s);
+ }
+ break;
+
+ case CHANNEL_EVENT_USER:
+ break;
+ }
+
+ if (error && drdynvc && drdynvc->rdpcontext)
+ setChannelError(drdynvc->rdpcontext, error,
+ "drdynvc_virtual_channel_open_event reported an error");
+}
+
+static DWORD WINAPI drdynvc_virtual_channel_client_thread(LPVOID arg)
+{
+ /* TODO: rewrite this */
+ wStream* data = NULL;
+ wMessage message = { 0 };
+ UINT error = CHANNEL_RC_OK;
+ drdynvcPlugin* drdynvc = (drdynvcPlugin*)arg;
+
+ if (!drdynvc)
+ {
+ ExitThread((DWORD)CHANNEL_RC_BAD_CHANNEL_HANDLE);
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+ }
+
+ while (1)
+ {
+ if (!MessageQueue_Wait(drdynvc->queue))
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "MessageQueue_Wait failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (!MessageQueue_Peek(drdynvc->queue, &message, TRUE))
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "MessageQueue_Peek failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (message.id == WMQ_QUIT)
+ break;
+
+ if (message.id == 0)
+ {
+ UINT32 ThreadingFlags = TRUE;
+ data = (wStream*)message.wParam;
+
+ if ((error = drdynvc_order_recv(drdynvc, data, ThreadingFlags)))
+ {
+ WLog_Print(drdynvc->log, WLOG_WARN,
+ "drdynvc_order_recv failed with error %" PRIu32 "!", error);
+ }
+
+ Stream_Release(data);
+ }
+ }
+
+ {
+ /* Disconnect remaining dynamic channels that the server did not.
+ * This is required to properly shut down channels by calling the appropriate
+ * event handlers. */
+ DVCMAN* drdynvcMgr = (DVCMAN*)drdynvc->channel_mgr;
+
+ HashTable_Clear(drdynvcMgr->channelsById);
+ }
+
+ if (error && drdynvc->rdpcontext)
+ setChannelError(drdynvc->rdpcontext, error,
+ "drdynvc_virtual_channel_client_thread reported an error");
+
+ ExitThread((DWORD)error);
+ return error;
+}
+
+static void drdynvc_queue_object_free(void* obj)
+{
+ wStream* s = NULL;
+ wMessage* msg = (wMessage*)obj;
+
+ if (!msg || (msg->id != 0))
+ return;
+
+ s = (wStream*)msg->wParam;
+
+ if (s)
+ Stream_Release(s);
+}
+
+static UINT drdynvc_virtual_channel_event_initialized(drdynvcPlugin* drdynvc, LPVOID pData,
+ UINT32 dataLength)
+{
+ wObject* obj = NULL;
+ WINPR_UNUSED(pData);
+ WINPR_UNUSED(dataLength);
+
+ if (!drdynvc)
+ goto error;
+
+ drdynvc->queue = MessageQueue_New(NULL);
+
+ if (!drdynvc->queue)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "MessageQueue_New failed!");
+ goto error;
+ }
+
+ obj = MessageQueue_Object(drdynvc->queue);
+ obj->fnObjectFree = drdynvc_queue_object_free;
+ drdynvc->channel_mgr = dvcman_new(drdynvc);
+
+ if (!drdynvc->channel_mgr)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "dvcman_new failed!");
+ goto error;
+ }
+
+ return CHANNEL_RC_OK;
+error:
+ return ERROR_INTERNAL_ERROR;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drdynvc_virtual_channel_event_connected(drdynvcPlugin* drdynvc, LPVOID pData,
+ UINT32 dataLength)
+{
+ UINT error = 0;
+ UINT32 status = 0;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(drdynvc);
+ WINPR_UNUSED(pData);
+ WINPR_UNUSED(dataLength);
+
+ if (!drdynvc)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ WINPR_ASSERT(drdynvc->channelEntryPoints.pVirtualChannelOpenEx);
+ status = drdynvc->channelEntryPoints.pVirtualChannelOpenEx(
+ drdynvc->InitHandle, &drdynvc->OpenHandle, drdynvc->channelDef.name,
+ drdynvc_virtual_channel_open_event_ex);
+
+ if (status != CHANNEL_RC_OK)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "pVirtualChannelOpen failed with %s [%08" PRIX32 "]",
+ WTSErrorToString(status), status);
+ return status;
+ }
+
+ WINPR_ASSERT(drdynvc->rdpcontext);
+ settings = drdynvc->rdpcontext->settings;
+ WINPR_ASSERT(settings);
+
+ for (UINT32 index = 0;
+ index < freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelCount); index++)
+ {
+ const ADDIN_ARGV* args =
+ freerdp_settings_get_pointer_array(settings, FreeRDP_DynamicChannelArray, index);
+ error = dvcman_load_addin(drdynvc, drdynvc->channel_mgr, args, drdynvc->rdpcontext);
+
+ if (CHANNEL_RC_OK != error)
+ goto error;
+ }
+
+ if ((error = dvcman_init(drdynvc, drdynvc->channel_mgr)))
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "dvcman_init failed with error %" PRIu32 "!", error);
+ goto error;
+ }
+
+ drdynvc->state = DRDYNVC_STATE_CAPABILITIES;
+
+ if (drdynvc->async)
+ {
+ if (!(drdynvc->thread = CreateThread(NULL, 0, drdynvc_virtual_channel_client_thread,
+ (void*)drdynvc, 0, NULL)))
+ {
+ error = ERROR_INTERNAL_ERROR;
+ WLog_Print(drdynvc->log, WLOG_ERROR, "CreateThread failed!");
+ goto error;
+ }
+ }
+
+error:
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drdynvc_virtual_channel_event_disconnected(drdynvcPlugin* drdynvc)
+{
+ UINT status = 0;
+
+ if (!drdynvc)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ if (drdynvc->OpenHandle == 0)
+ return CHANNEL_RC_OK;
+
+ if (drdynvc->queue)
+ {
+ if (!MessageQueue_PostQuit(drdynvc->queue, 0))
+ {
+ status = GetLastError();
+ WLog_Print(drdynvc->log, WLOG_ERROR,
+ "MessageQueue_PostQuit failed with error %" PRIu32 "", status);
+ return status;
+ }
+ }
+
+ if (drdynvc->thread)
+ {
+ if (WaitForSingleObject(drdynvc->thread, INFINITE) != WAIT_OBJECT_0)
+ {
+ status = GetLastError();
+ WLog_Print(drdynvc->log, WLOG_ERROR,
+ "WaitForSingleObject failed with error %" PRIu32 "", status);
+ return status;
+ }
+
+ CloseHandle(drdynvc->thread);
+ drdynvc->thread = NULL;
+ }
+ else
+ {
+ {
+ /* Disconnect remaining dynamic channels that the server did not.
+ * This is required to properly shut down channels by calling the appropriate
+ * event handlers. */
+ DVCMAN* drdynvcMgr = (DVCMAN*)drdynvc->channel_mgr;
+
+ HashTable_Clear(drdynvcMgr->channelsById);
+ }
+ }
+
+ WINPR_ASSERT(drdynvc->channelEntryPoints.pVirtualChannelCloseEx);
+ status = drdynvc->channelEntryPoints.pVirtualChannelCloseEx(drdynvc->InitHandle,
+ drdynvc->OpenHandle);
+
+ if (status != CHANNEL_RC_OK)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "pVirtualChannelClose failed with %s [%08" PRIX32 "]",
+ WTSErrorToString(status), status);
+ }
+
+ dvcman_clear(drdynvc, drdynvc->channel_mgr);
+ if (drdynvc->queue)
+ MessageQueue_Clear(drdynvc->queue);
+ drdynvc->OpenHandle = 0;
+
+ if (drdynvc->data_in)
+ {
+ Stream_Release(drdynvc->data_in);
+ drdynvc->data_in = NULL;
+ }
+
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drdynvc_virtual_channel_event_terminated(drdynvcPlugin* drdynvc)
+{
+ if (!drdynvc)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ MessageQueue_Free(drdynvc->queue);
+ drdynvc->queue = NULL;
+
+ if (drdynvc->channel_mgr)
+ {
+ dvcman_free(drdynvc, drdynvc->channel_mgr);
+ drdynvc->channel_mgr = NULL;
+ }
+ drdynvc->InitHandle = 0;
+ free(drdynvc->context);
+ free(drdynvc);
+ return CHANNEL_RC_OK;
+}
+
+static UINT drdynvc_virtual_channel_event_attached(drdynvcPlugin* drdynvc)
+{
+ UINT error = CHANNEL_RC_OK;
+ DVCMAN* dvcman = NULL;
+
+ if (!drdynvc)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ dvcman = (DVCMAN*)drdynvc->channel_mgr;
+
+ if (!dvcman)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ ArrayList_Lock(dvcman->plugins);
+ for (size_t i = 0; i < ArrayList_Count(dvcman->plugins); i++)
+ {
+ IWTSPlugin* pPlugin = ArrayList_GetItem(dvcman->plugins, i);
+
+ error = IFCALLRESULT(CHANNEL_RC_OK, pPlugin->Attached, pPlugin);
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "Attach failed with error %" PRIu32 "!", error);
+ goto fail;
+ }
+ }
+
+fail:
+ ArrayList_Unlock(dvcman->plugins);
+ return error;
+}
+
+static UINT drdynvc_virtual_channel_event_detached(drdynvcPlugin* drdynvc)
+{
+ UINT error = CHANNEL_RC_OK;
+ DVCMAN* dvcman = NULL;
+
+ if (!drdynvc)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ dvcman = (DVCMAN*)drdynvc->channel_mgr;
+
+ if (!dvcman)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ ArrayList_Lock(dvcman->plugins);
+ for (size_t i = 0; i < ArrayList_Count(dvcman->plugins); i++)
+ {
+ IWTSPlugin* pPlugin = ArrayList_GetItem(dvcman->plugins, i);
+
+ error = IFCALLRESULT(CHANNEL_RC_OK, pPlugin->Detached, pPlugin);
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "Detach failed with error %" PRIu32 "!", error);
+ goto fail;
+ }
+ }
+
+fail:
+ ArrayList_Unlock(dvcman->plugins);
+
+ return error;
+}
+
+static VOID VCAPITYPE drdynvc_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle,
+ UINT event, LPVOID pData,
+ UINT dataLength)
+{
+ UINT error = CHANNEL_RC_OK;
+ drdynvcPlugin* drdynvc = (drdynvcPlugin*)lpUserParam;
+
+ if (!drdynvc || (drdynvc->InitHandle != pInitHandle))
+ {
+ WLog_ERR(TAG, "drdynvc_virtual_channel_init_event: error no match");
+ return;
+ }
+
+ switch (event)
+ {
+ case CHANNEL_EVENT_INITIALIZED:
+ error = drdynvc_virtual_channel_event_initialized(drdynvc, pData, dataLength);
+ break;
+ case CHANNEL_EVENT_CONNECTED:
+ if ((error = drdynvc_virtual_channel_event_connected(drdynvc, pData, dataLength)))
+ WLog_Print(drdynvc->log, WLOG_ERROR,
+ "drdynvc_virtual_channel_event_connected failed with error %" PRIu32 "",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_DISCONNECTED:
+ if ((error = drdynvc_virtual_channel_event_disconnected(drdynvc)))
+ WLog_Print(drdynvc->log, WLOG_ERROR,
+ "drdynvc_virtual_channel_event_disconnected failed with error %" PRIu32
+ "",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_TERMINATED:
+ if ((error = drdynvc_virtual_channel_event_terminated(drdynvc)))
+ WLog_Print(drdynvc->log, WLOG_ERROR,
+ "drdynvc_virtual_channel_event_terminated failed with error %" PRIu32 "",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_ATTACHED:
+ if ((error = drdynvc_virtual_channel_event_attached(drdynvc)))
+ WLog_Print(drdynvc->log, WLOG_ERROR,
+ "drdynvc_virtual_channel_event_attached failed with error %" PRIu32 "",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_DETACHED:
+ if ((error = drdynvc_virtual_channel_event_detached(drdynvc)))
+ WLog_Print(drdynvc->log, WLOG_ERROR,
+ "drdynvc_virtual_channel_event_detached failed with error %" PRIu32 "",
+ error);
+
+ break;
+
+ default:
+ break;
+ }
+
+ if (error && drdynvc->rdpcontext)
+ setChannelError(drdynvc->rdpcontext, error,
+ "drdynvc_virtual_channel_init_event_ex reported an error");
+}
+
+/**
+ * Channel Client Interface
+ */
+
+static int drdynvc_get_version(DrdynvcClientContext* context)
+{
+ WINPR_ASSERT(context);
+ drdynvcPlugin* drdynvc = (drdynvcPlugin*)context->handle;
+ WINPR_ASSERT(drdynvc);
+ return drdynvc->version;
+}
+
+/* drdynvc is always built-in */
+#define VirtualChannelEntryEx drdynvc_VirtualChannelEntryEx
+
+FREERDP_ENTRY_POINT(BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS_EX pEntryPoints,
+ PVOID pInitHandle))
+{
+ UINT rc = 0;
+ drdynvcPlugin* drdynvc = NULL;
+ DrdynvcClientContext* context = NULL;
+ CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx = NULL;
+ drdynvc = (drdynvcPlugin*)calloc(1, sizeof(drdynvcPlugin));
+
+ WINPR_ASSERT(pEntryPoints);
+ if (!drdynvc)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return FALSE;
+ }
+
+ drdynvc->channelDef.options =
+ CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP | CHANNEL_OPTION_COMPRESS_RDP;
+ sprintf_s(drdynvc->channelDef.name, ARRAYSIZE(drdynvc->channelDef.name),
+ DRDYNVC_SVC_CHANNEL_NAME);
+ drdynvc->state = DRDYNVC_STATE_INITIAL;
+ pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints;
+
+ if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) &&
+ (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER))
+ {
+ context = (DrdynvcClientContext*)calloc(1, sizeof(DrdynvcClientContext));
+
+ if (!context)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "calloc failed!");
+ free(drdynvc);
+ return FALSE;
+ }
+
+ context->handle = (void*)drdynvc;
+ context->custom = NULL;
+ drdynvc->context = context;
+ context->GetVersion = drdynvc_get_version;
+ drdynvc->rdpcontext = pEntryPointsEx->context;
+ if (!freerdp_settings_get_bool(drdynvc->rdpcontext->settings,
+ FreeRDP_TransportDumpReplay) &&
+ !freerdp_settings_get_bool(drdynvc->rdpcontext->settings,
+ FreeRDP_SynchronousDynamicChannels))
+ drdynvc->async = TRUE;
+ }
+
+ drdynvc->log = WLog_Get(TAG);
+ WLog_Print(drdynvc->log, WLOG_DEBUG, "VirtualChannelEntryEx");
+ CopyMemory(&(drdynvc->channelEntryPoints), pEntryPoints,
+ sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX));
+ drdynvc->InitHandle = pInitHandle;
+
+ WINPR_ASSERT(drdynvc->channelEntryPoints.pVirtualChannelInitEx);
+ rc = drdynvc->channelEntryPoints.pVirtualChannelInitEx(
+ drdynvc, context, pInitHandle, &drdynvc->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000,
+ drdynvc_virtual_channel_init_event_ex);
+
+ if (CHANNEL_RC_OK != rc)
+ {
+ WLog_Print(drdynvc->log, WLOG_ERROR, "pVirtualChannelInit failed with %s [%08" PRIX32 "]",
+ WTSErrorToString(rc), rc);
+ free(drdynvc->context);
+ free(drdynvc);
+ return FALSE;
+ }
+
+ drdynvc->channelEntryPoints.pInterface = context;
+ return TRUE;
+}
diff --git a/channels/drdynvc/client/drdynvc_main.h b/channels/drdynvc/client/drdynvc_main.h
new file mode 100644
index 0000000..c282cdd
--- /dev/null
+++ b/channels/drdynvc/client/drdynvc_main.h
@@ -0,0 +1,133 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Dynamic Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_DRDYNVC_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_DRDYNVC_CLIENT_MAIN_H
+
+#include <winpr/wlog.h>
+#include <winpr/synch.h>
+#include <freerdp/settings.h>
+#include <winpr/collections.h>
+
+#include <freerdp/api.h>
+#include <freerdp/svc.h>
+#include <freerdp/dvc.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/client/drdynvc.h>
+#include <freerdp/freerdp.h>
+
+typedef struct drdynvc_plugin drdynvcPlugin;
+
+typedef struct
+{
+ IWTSVirtualChannelManager iface;
+
+ drdynvcPlugin* drdynvc;
+
+ wArrayList* plugin_names;
+ wArrayList* plugins;
+
+ wHashTable* listeners;
+ wHashTable* channelsById;
+ wStreamPool* pool;
+} DVCMAN;
+
+typedef struct
+{
+ IWTSListener iface;
+
+ DVCMAN* dvcman;
+ char* channel_name;
+ UINT32 flags;
+ IWTSListenerCallback* listener_callback;
+} DVCMAN_LISTENER;
+
+typedef struct
+{
+ IDRDYNVC_ENTRY_POINTS iface;
+
+ DVCMAN* dvcman;
+ const ADDIN_ARGV* args;
+ rdpContext* context;
+} DVCMAN_ENTRY_POINTS;
+
+typedef enum
+{
+ DVC_CHANNEL_INIT,
+ DVC_CHANNEL_RUNNING,
+ DVC_CHANNEL_CLOSED
+} DVC_CHANNEL_STATE;
+
+typedef struct
+{
+ IWTSVirtualChannel iface;
+
+ volatile LONG refCounter;
+ DVC_CHANNEL_STATE state;
+ DVCMAN* dvcman;
+ void* pInterface;
+ UINT32 channel_id;
+ char* channel_name;
+ IWTSVirtualChannelCallback* channel_callback;
+
+ wStream* dvc_data;
+ UINT32 dvc_data_length;
+ CRITICAL_SECTION lock;
+} DVCMAN_CHANNEL;
+
+typedef enum
+{
+ DRDYNVC_STATE_INITIAL,
+ DRDYNVC_STATE_CAPABILITIES,
+ DRDYNVC_STATE_READY,
+ DRDYNVC_STATE_OPENING_CHANNEL,
+ DRDYNVC_STATE_SEND_RECEIVE,
+ DRDYNVC_STATE_FINAL
+} DRDYNVC_STATE;
+
+struct drdynvc_plugin
+{
+ CHANNEL_DEF channelDef;
+ CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints;
+
+ wLog* log;
+ HANDLE thread;
+ BOOL async;
+ wStream* data_in;
+ void* InitHandle;
+ DWORD OpenHandle;
+ wMessageQueue* queue;
+
+ DRDYNVC_STATE state;
+ DrdynvcClientContext* context;
+
+ UINT16 version;
+ int PriorityCharge0;
+ int PriorityCharge1;
+ int PriorityCharge2;
+ int PriorityCharge3;
+ rdpContext* rdpcontext;
+
+ IWTSVirtualChannelManager* channel_mgr;
+};
+
+#endif /* FREERDP_CHANNEL_DRDYNVC_CLIENT_MAIN_H */
diff --git a/channels/drdynvc/server/CMakeLists.txt b/channels/drdynvc/server/CMakeLists.txt
new file mode 100644
index 0000000..85b1bd6
--- /dev/null
+++ b/channels/drdynvc/server/CMakeLists.txt
@@ -0,0 +1,28 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_server("drdynvc")
+
+set(${MODULE_PREFIX}_SRCS
+ drdynvc_main.c
+ drdynvc_main.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry")
diff --git a/channels/drdynvc/server/drdynvc_main.c b/channels/drdynvc/server/drdynvc_main.c
new file mode 100644
index 0000000..d8cbf24
--- /dev/null
+++ b/channels/drdynvc/server/drdynvc_main.c
@@ -0,0 +1,204 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Dynamic Virtual Channel Extension
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/channels/drdynvc.h>
+
+#include "drdynvc_main.h"
+
+#define TAG CHANNELS_TAG("drdynvc.server")
+
+static DWORD WINAPI drdynvc_server_thread(LPVOID arg)
+{
+#if 0
+ wStream* s;
+ DWORD status;
+ DWORD nCount;
+ void* buffer;
+ HANDLE events[8];
+ HANDLE ChannelEvent;
+ DWORD BytesReturned;
+ DrdynvcServerContext* context;
+ UINT error = ERROR_INTERNAL_ERROR;
+ context = (DrdynvcServerContext*) arg;
+ buffer = NULL;
+ BytesReturned = 0;
+ ChannelEvent = NULL;
+
+ s = Stream_New(NULL, 4096);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ ExitThread((DWORD) CHANNEL_RC_NO_MEMORY);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if (WTSVirtualChannelQuery(context->priv->ChannelHandle, WTSVirtualEventHandle,
+ &buffer, &BytesReturned) == TRUE)
+ {
+ if (BytesReturned == sizeof(HANDLE))
+ CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE));
+
+ WTSFreeMemory(buffer);
+ }
+
+ nCount = 0;
+ events[nCount++] = ChannelEvent;
+ events[nCount++] = context->priv->StopEvent;
+
+ while (1)
+ {
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+
+ if (WaitForSingleObject(context->priv->StopEvent, 0) == WAIT_OBJECT_0)
+ {
+ error = CHANNEL_RC_OK;
+ break;
+ }
+
+ if (!WTSVirtualChannelRead(context->priv->ChannelHandle, 0, NULL, 0,
+ &BytesReturned))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ break;
+ }
+
+ if (BytesReturned < 1)
+ continue;
+
+ if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ break;
+ }
+
+ if (!WTSVirtualChannelRead(context->priv->ChannelHandle, 0,
+ (PCHAR) Stream_Buffer(s), Stream_Capacity(s), &BytesReturned))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ break;
+ }
+ }
+
+ Stream_Free(s, TRUE);
+ ExitThread((DWORD) error);
+#endif
+ // WTF ... this code only reads data into the stream until there is no more memory
+ ExitThread(0);
+ return 0;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drdynvc_server_start(DrdynvcServerContext* context)
+{
+ context->priv->ChannelHandle =
+ WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, DRDYNVC_SVC_CHANNEL_NAME);
+
+ if (!context->priv->ChannelHandle)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelOpen failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if (!(context->priv->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!(context->priv->Thread =
+ CreateThread(NULL, 0, drdynvc_server_thread, (void*)context, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ CloseHandle(context->priv->StopEvent);
+ context->priv->StopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drdynvc_server_stop(DrdynvcServerContext* context)
+{
+ UINT error = 0;
+ SetEvent(context->priv->StopEvent);
+
+ if (WaitForSingleObject(context->priv->Thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ CloseHandle(context->priv->Thread);
+ return CHANNEL_RC_OK;
+}
+
+DrdynvcServerContext* drdynvc_server_context_new(HANDLE vcm)
+{
+ DrdynvcServerContext* context = NULL;
+ context = (DrdynvcServerContext*)calloc(1, sizeof(DrdynvcServerContext));
+
+ if (context)
+ {
+ context->vcm = vcm;
+ context->Start = drdynvc_server_start;
+ context->Stop = drdynvc_server_stop;
+ context->priv = (DrdynvcServerPrivate*)calloc(1, sizeof(DrdynvcServerPrivate));
+
+ if (!context->priv)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ free(context);
+ return NULL;
+ }
+ }
+ else
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ }
+
+ return context;
+}
+
+void drdynvc_server_context_free(DrdynvcServerContext* context)
+{
+ if (context)
+ {
+ free(context->priv);
+ free(context);
+ }
+}
diff --git a/channels/drdynvc/server/drdynvc_main.h b/channels/drdynvc/server/drdynvc_main.h
new file mode 100644
index 0000000..4456d95
--- /dev/null
+++ b/channels/drdynvc/server/drdynvc_main.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Dynamic Virtual Channel Extension
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_DRDYNVC_SERVER_MAIN_H
+#define FREERDP_CHANNEL_DRDYNVC_SERVER_MAIN_H
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+#include <freerdp/settings.h>
+#include <freerdp/server/drdynvc.h>
+
+struct s_drdynvc_server_private
+{
+ HANDLE Thread;
+ HANDLE StopEvent;
+ void* ChannelHandle;
+};
+
+#endif /* FREERDP_CHANNEL_DRDYNVC_SERVER_MAIN_H */
diff --git a/channels/drive/CMakeLists.txt b/channels/drive/CMakeLists.txt
new file mode 100644
index 0000000..f2d2c5a
--- /dev/null
+++ b/channels/drive/CMakeLists.txt
@@ -0,0 +1,23 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("drive")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
+
diff --git a/channels/drive/ChannelOptions.cmake b/channels/drive/ChannelOptions.cmake
new file mode 100644
index 0000000..0792c1e
--- /dev/null
+++ b/channels/drive/ChannelOptions.cmake
@@ -0,0 +1,13 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT OFF)
+
+define_channel_options(NAME "drive" TYPE "device"
+ DESCRIPTION "Drive Redirection Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPEFS]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/drive/client/CMakeLists.txt b/channels/drive/client/CMakeLists.txt
new file mode 100644
index 0000000..a236f76
--- /dev/null
+++ b/channels/drive/client/CMakeLists.txt
@@ -0,0 +1,29 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("drive")
+
+set(${MODULE_PREFIX}_SRCS
+ drive_file.c
+ drive_file.h
+ drive_main.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr freerdp
+)
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DeviceServiceEntry")
diff --git a/channels/drive/client/drive_file.c b/channels/drive/client/drive_file.c
new file mode 100644
index 0000000..31150ea
--- /dev/null
+++ b/channels/drive/client/drive_file.c
@@ -0,0 +1,971 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * File System Virtual Channel
+ *
+ * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2012 Gerald Richter
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 Inuvika Inc.
+ * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
+ * Copyright 2017 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2017 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/string.h>
+#include <winpr/path.h>
+#include <winpr/file.h>
+#include <winpr/stream.h>
+
+#include <freerdp/channels/rdpdr.h>
+
+#include "drive_file.h"
+
+#ifdef WITH_DEBUG_RDPDR
+#define DEBUG_WSTR(msg, wstr) \
+ do \
+ { \
+ char lpstr[1024] = { 0 }; \
+ ConvertWCharToUtf8(wstr, lpstr, ARRAYSIZE(lpstr)); \
+ WLog_DBG(TAG, msg, lpstr); \
+ } while (0)
+#else
+#define DEBUG_WSTR(msg, wstr) \
+ do \
+ { \
+ } while (0)
+#endif
+
+static BOOL drive_file_fix_path(WCHAR* path, size_t length)
+{
+ if ((length == 0) || (length > UINT32_MAX))
+ return FALSE;
+
+ WINPR_ASSERT(path);
+
+ for (size_t i = 0; i < length; i++)
+ {
+ if (path[i] == L'\\')
+ path[i] = L'/';
+ }
+
+#ifdef WIN32
+
+ if ((length == 3) && (path[1] == L':') && (path[2] == L'/'))
+ return FALSE;
+
+#else
+
+ if ((length == 1) && (path[0] == L'/'))
+ return FALSE;
+
+#endif
+
+ if ((length > 0) && (path[length - 1] == L'/'))
+ path[length - 1] = L'\0';
+
+ return TRUE;
+}
+
+static WCHAR* drive_file_combine_fullpath(const WCHAR* base_path, const WCHAR* path,
+ size_t PathWCharLength)
+{
+ BOOL ok = FALSE;
+ WCHAR* fullpath = NULL;
+ size_t length = 0;
+
+ if (!base_path || (!path && (PathWCharLength > 0)))
+ goto fail;
+
+ const size_t base_path_length = _wcsnlen(base_path, MAX_PATH);
+ length = base_path_length + PathWCharLength + 1;
+ fullpath = (WCHAR*)calloc(length, sizeof(WCHAR));
+
+ if (!fullpath)
+ goto fail;
+
+ CopyMemory(fullpath, base_path, base_path_length * sizeof(WCHAR));
+ if (path)
+ CopyMemory(&fullpath[base_path_length], path, PathWCharLength * sizeof(WCHAR));
+
+ if (!drive_file_fix_path(fullpath, length))
+ goto fail;
+
+ /* Ensure the path does not contain sequences like '..' */
+ WCHAR dotdotbuffer[6] = { 0 };
+ const WCHAR* dotdot = InitializeConstWCharFromUtf8("..", dotdotbuffer, ARRAYSIZE(dotdotbuffer));
+ if (_wcsstr(&fullpath[base_path_length], dotdot))
+ {
+ char abuffer[MAX_PATH] = { 0 };
+ ConvertWCharToUtf8(&fullpath[base_path_length], abuffer, ARRAYSIZE(abuffer));
+
+ WLog_WARN(TAG, "[rdpdr] received invalid file path '%s' from server, aborting!",
+ &abuffer[base_path_length]);
+ goto fail;
+ }
+
+ ok = TRUE;
+fail:
+ if (!ok)
+ {
+ free(fullpath);
+ fullpath = NULL;
+ }
+ return fullpath;
+}
+
+static BOOL drive_file_set_fullpath(DRIVE_FILE* file, WCHAR* fullpath)
+{
+ if (!file || !fullpath)
+ return FALSE;
+
+ const size_t len = _wcslen(fullpath);
+ free(file->fullpath);
+ file->fullpath = NULL;
+
+ if (len == 0)
+ return TRUE;
+
+ file->fullpath = fullpath;
+
+ const WCHAR sep[] = { PathGetSeparatorW(PATH_STYLE_NATIVE), '\0' };
+ WCHAR* filename = _wcsrchr(file->fullpath, *sep);
+ if (filename && _wcsncmp(filename, sep, ARRAYSIZE(sep)) == 0)
+ *filename = '\0';
+
+ return TRUE;
+}
+
+static BOOL drive_file_init(DRIVE_FILE* file)
+{
+ UINT CreateDisposition = 0;
+ DWORD dwAttr = GetFileAttributesW(file->fullpath);
+
+ if (dwAttr != INVALID_FILE_ATTRIBUTES)
+ {
+ /* The file exists */
+ file->is_dir = (dwAttr & FILE_ATTRIBUTE_DIRECTORY) != 0;
+
+ if (file->is_dir)
+ {
+ if (file->CreateDisposition == FILE_CREATE)
+ {
+ SetLastError(ERROR_ALREADY_EXISTS);
+ return FALSE;
+ }
+
+ if (file->CreateOptions & FILE_NON_DIRECTORY_FILE)
+ {
+ SetLastError(ERROR_ACCESS_DENIED);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+ else
+ {
+ if (file->CreateOptions & FILE_DIRECTORY_FILE)
+ {
+ SetLastError(ERROR_DIRECTORY);
+ return FALSE;
+ }
+ }
+ }
+ else
+ {
+ file->is_dir = ((file->CreateOptions & FILE_DIRECTORY_FILE) ? TRUE : FALSE);
+
+ if (file->is_dir)
+ {
+ /* Should only create the directory if the disposition allows for it */
+ if ((file->CreateDisposition == FILE_OPEN_IF) ||
+ (file->CreateDisposition == FILE_CREATE))
+ {
+ if (CreateDirectoryW(file->fullpath, NULL) != 0)
+ {
+ return TRUE;
+ }
+ }
+
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ return FALSE;
+ }
+ }
+
+ if (file->file_handle == INVALID_HANDLE_VALUE)
+ {
+ switch (file->CreateDisposition)
+ {
+ case FILE_SUPERSEDE: /* If the file already exists, replace it with the given file. If
+ it does not, create the given file. */
+ CreateDisposition = CREATE_ALWAYS;
+ break;
+
+ case FILE_OPEN: /* If the file already exists, open it instead of creating a new file.
+ If it does not, fail the request and do not create a new file. */
+ CreateDisposition = OPEN_EXISTING;
+ break;
+
+ case FILE_CREATE: /* If the file already exists, fail the request and do not create or
+ open the given file. If it does not, create the given file. */
+ CreateDisposition = CREATE_NEW;
+ break;
+
+ case FILE_OPEN_IF: /* If the file already exists, open it. If it does not, create the
+ given file. */
+ CreateDisposition = OPEN_ALWAYS;
+ break;
+
+ case FILE_OVERWRITE: /* If the file already exists, open it and overwrite it. If it does
+ not, fail the request. */
+ CreateDisposition = TRUNCATE_EXISTING;
+ break;
+
+ case FILE_OVERWRITE_IF: /* If the file already exists, open it and overwrite it. If it
+ does not, create the given file. */
+ CreateDisposition = CREATE_ALWAYS;
+ break;
+
+ default:
+ break;
+ }
+
+#ifndef WIN32
+ file->SharedAccess = 0;
+#endif
+ file->file_handle = CreateFileW(file->fullpath, file->DesiredAccess, file->SharedAccess,
+ NULL, CreateDisposition, file->FileAttributes, NULL);
+ }
+
+#ifdef WIN32
+ if (file->file_handle == INVALID_HANDLE_VALUE)
+ {
+ /* Get the error message, if any. */
+ DWORD errorMessageID = GetLastError();
+
+ if (errorMessageID != 0)
+ {
+ LPSTR messageBuffer = NULL;
+ size_t size =
+ FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPSTR)&messageBuffer, 0, NULL);
+ WLog_ERR(TAG, "Error in drive_file_init: %s %s", messageBuffer, file->fullpath);
+ /* Free the buffer. */
+ LocalFree(messageBuffer);
+ /* restore original error code */
+ SetLastError(errorMessageID);
+ }
+ }
+#endif
+
+ return file->file_handle != INVALID_HANDLE_VALUE;
+}
+
+DRIVE_FILE* drive_file_new(const WCHAR* base_path, const WCHAR* path, UINT32 PathWCharLength,
+ UINT32 id, UINT32 DesiredAccess, UINT32 CreateDisposition,
+ UINT32 CreateOptions, UINT32 FileAttributes, UINT32 SharedAccess)
+{
+ DRIVE_FILE* file = NULL;
+
+ if (!base_path || (!path && (PathWCharLength > 0)))
+ return NULL;
+
+ file = (DRIVE_FILE*)calloc(1, sizeof(DRIVE_FILE));
+
+ if (!file)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return NULL;
+ }
+
+ file->file_handle = INVALID_HANDLE_VALUE;
+ file->find_handle = INVALID_HANDLE_VALUE;
+ file->id = id;
+ file->basepath = base_path;
+ file->FileAttributes = FileAttributes;
+ file->DesiredAccess = DesiredAccess;
+ file->CreateDisposition = CreateDisposition;
+ file->CreateOptions = CreateOptions;
+ file->SharedAccess = SharedAccess;
+ drive_file_set_fullpath(file, drive_file_combine_fullpath(base_path, path, PathWCharLength));
+
+ if (!drive_file_init(file))
+ {
+ DWORD lastError = GetLastError();
+ drive_file_free(file);
+ SetLastError(lastError);
+ return NULL;
+ }
+
+ return file;
+}
+
+BOOL drive_file_free(DRIVE_FILE* file)
+{
+ BOOL rc = FALSE;
+
+ if (!file)
+ return FALSE;
+
+ if (file->file_handle != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(file->file_handle);
+ file->file_handle = INVALID_HANDLE_VALUE;
+ }
+
+ if (file->find_handle != INVALID_HANDLE_VALUE)
+ {
+ FindClose(file->find_handle);
+ file->find_handle = INVALID_HANDLE_VALUE;
+ }
+
+ if (file->delete_pending)
+ {
+ if (file->is_dir)
+ {
+ if (!winpr_RemoveDirectory_RecursiveW(file->fullpath))
+ goto fail;
+ }
+ else if (!DeleteFileW(file->fullpath))
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ DEBUG_WSTR("Free %s", file->fullpath);
+ free(file->fullpath);
+ free(file);
+ return rc;
+}
+
+BOOL drive_file_seek(DRIVE_FILE* file, UINT64 Offset)
+{
+ LARGE_INTEGER loffset;
+
+ if (!file)
+ return FALSE;
+
+ if (Offset > INT64_MAX)
+ return FALSE;
+
+ loffset.QuadPart = (LONGLONG)Offset;
+ return SetFilePointerEx(file->file_handle, loffset, NULL, FILE_BEGIN);
+}
+
+BOOL drive_file_read(DRIVE_FILE* file, BYTE* buffer, UINT32* Length)
+{
+ DWORD read = 0;
+
+ if (!file || !buffer || !Length)
+ return FALSE;
+
+ DEBUG_WSTR("Read file %s", file->fullpath);
+
+ if (ReadFile(file->file_handle, buffer, *Length, &read, NULL))
+ {
+ *Length = read;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL drive_file_write(DRIVE_FILE* file, const BYTE* buffer, UINT32 Length)
+{
+ DWORD written = 0;
+
+ if (!file || !buffer)
+ return FALSE;
+
+ DEBUG_WSTR("Write file %s", file->fullpath);
+
+ while (Length > 0)
+ {
+ if (!WriteFile(file->file_handle, buffer, Length, &written, NULL))
+ return FALSE;
+
+ Length -= written;
+ buffer += written;
+ }
+
+ return TRUE;
+}
+
+static BOOL drive_file_query_from_handle_information(const DRIVE_FILE* file,
+ const BY_HANDLE_FILE_INFORMATION* info,
+ UINT32 FsInformationClass, wStream* output)
+{
+ switch (FsInformationClass)
+ {
+ case FileBasicInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232094.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 36))
+ return FALSE;
+
+ Stream_Write_UINT32(output, 36); /* Length */
+ Stream_Write_UINT32(output, info->ftCreationTime.dwLowDateTime); /* CreationTime */
+ Stream_Write_UINT32(output, info->ftCreationTime.dwHighDateTime); /* CreationTime */
+ Stream_Write_UINT32(output, info->ftLastAccessTime.dwLowDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(output, info->ftLastAccessTime.dwHighDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(output, info->ftLastWriteTime.dwLowDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output, info->ftLastWriteTime.dwHighDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output, info->ftLastWriteTime.dwLowDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output, info->ftLastWriteTime.dwHighDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output, info->dwFileAttributes); /* FileAttributes */
+ /* Reserved(4), MUST NOT be added! */
+ break;
+
+ case FileStandardInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232088.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 22))
+ return FALSE;
+
+ Stream_Write_UINT32(output, 22); /* Length */
+ Stream_Write_UINT32(output, info->nFileSizeLow); /* AllocationSize */
+ Stream_Write_UINT32(output, info->nFileSizeHigh); /* AllocationSize */
+ Stream_Write_UINT32(output, info->nFileSizeLow); /* EndOfFile */
+ Stream_Write_UINT32(output, info->nFileSizeHigh); /* EndOfFile */
+ Stream_Write_UINT32(output, info->nNumberOfLinks); /* NumberOfLinks */
+ Stream_Write_UINT8(output, file->delete_pending ? 1 : 0); /* DeletePending */
+ Stream_Write_UINT8(output, info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
+ ? TRUE
+ : FALSE); /* Directory */
+ /* Reserved(2), MUST NOT be added! */
+ break;
+
+ case FileAttributeTagInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232093.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 8))
+ return FALSE;
+
+ Stream_Write_UINT32(output, 8); /* Length */
+ Stream_Write_UINT32(output, info->dwFileAttributes); /* FileAttributes */
+ Stream_Write_UINT32(output, 0); /* ReparseTag */
+ break;
+
+ default:
+ /* Unhandled FsInformationClass */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL drive_file_query_from_attributes(const DRIVE_FILE* file,
+ const WIN32_FILE_ATTRIBUTE_DATA* attrib,
+ UINT32 FsInformationClass, wStream* output)
+{
+ switch (FsInformationClass)
+ {
+ case FileBasicInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232094.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 36))
+ return FALSE;
+
+ Stream_Write_UINT32(output, 36); /* Length */
+ Stream_Write_UINT32(output, attrib->ftCreationTime.dwLowDateTime); /* CreationTime */
+ Stream_Write_UINT32(output, attrib->ftCreationTime.dwHighDateTime); /* CreationTime */
+ Stream_Write_UINT32(output,
+ attrib->ftLastAccessTime.dwLowDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(output,
+ attrib->ftLastAccessTime.dwHighDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(output, attrib->ftLastWriteTime.dwLowDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output, attrib->ftLastWriteTime.dwHighDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output, attrib->ftLastWriteTime.dwLowDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output, attrib->ftLastWriteTime.dwHighDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output, attrib->dwFileAttributes); /* FileAttributes */
+ /* Reserved(4), MUST NOT be added! */
+ break;
+
+ case FileStandardInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232088.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 22))
+ return FALSE;
+
+ Stream_Write_UINT32(output, 22); /* Length */
+ Stream_Write_UINT32(output, attrib->nFileSizeLow); /* AllocationSize */
+ Stream_Write_UINT32(output, attrib->nFileSizeHigh); /* AllocationSize */
+ Stream_Write_UINT32(output, attrib->nFileSizeLow); /* EndOfFile */
+ Stream_Write_UINT32(output, attrib->nFileSizeHigh); /* EndOfFile */
+ Stream_Write_UINT32(output, 0); /* NumberOfLinks */
+ Stream_Write_UINT8(output, file->delete_pending ? 1 : 0); /* DeletePending */
+ Stream_Write_UINT8(output, attrib->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
+ ? TRUE
+ : FALSE); /* Directory */
+ /* Reserved(2), MUST NOT be added! */
+ break;
+
+ case FileAttributeTagInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232093.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 8))
+ return FALSE;
+
+ Stream_Write_UINT32(output, 8); /* Length */
+ Stream_Write_UINT32(output, attrib->dwFileAttributes); /* FileAttributes */
+ Stream_Write_UINT32(output, 0); /* ReparseTag */
+ break;
+
+ default:
+ /* Unhandled FsInformationClass */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL drive_file_query_information(DRIVE_FILE* file, UINT32 FsInformationClass, wStream* output)
+{
+ BY_HANDLE_FILE_INFORMATION fileInformation = { 0 };
+ BOOL status = 0;
+ HANDLE hFile = NULL;
+
+ if (!file || !output)
+ return FALSE;
+
+ hFile = CreateFileW(file->fullpath, 0, FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ status = GetFileInformationByHandle(hFile, &fileInformation);
+ CloseHandle(hFile);
+ if (!status)
+ goto out_fail;
+
+ if (!drive_file_query_from_handle_information(file, &fileInformation, FsInformationClass,
+ output))
+ goto out_fail;
+
+ return TRUE;
+ }
+
+ /* If we failed before (i.e. if information for a drive is queried) fall back to
+ * GetFileAttributesExW */
+ WIN32_FILE_ATTRIBUTE_DATA fileAttributes = { 0 };
+ if (!GetFileAttributesExW(file->fullpath, GetFileExInfoStandard, &fileAttributes))
+ goto out_fail;
+
+ if (!drive_file_query_from_attributes(file, &fileAttributes, FsInformationClass, output))
+ goto out_fail;
+
+ return TRUE;
+out_fail:
+ Stream_Write_UINT32(output, 0); /* Length */
+ return FALSE;
+}
+
+BOOL drive_file_set_information(DRIVE_FILE* file, UINT32 FsInformationClass, UINT32 Length,
+ wStream* input)
+{
+ INT64 size = 0;
+ WCHAR* fullpath = NULL;
+ ULARGE_INTEGER liCreationTime;
+ ULARGE_INTEGER liLastAccessTime;
+ ULARGE_INTEGER liLastWriteTime;
+ ULARGE_INTEGER liChangeTime;
+ FILETIME ftCreationTime;
+ FILETIME ftLastAccessTime;
+ FILETIME ftLastWriteTime;
+ FILETIME* pftCreationTime = NULL;
+ FILETIME* pftLastAccessTime = NULL;
+ FILETIME* pftLastWriteTime = NULL;
+ UINT32 FileAttributes = 0;
+ UINT32 FileNameLength = 0;
+ LARGE_INTEGER liSize;
+ UINT8 delete_pending = 0;
+ UINT8 ReplaceIfExists = 0;
+ DWORD attr = 0;
+
+ if (!file || !input)
+ return FALSE;
+
+ switch (FsInformationClass)
+ {
+ case FileBasicInformation:
+ if (!Stream_CheckAndLogRequiredLength(TAG, input, 36))
+ return FALSE;
+
+ /* http://msdn.microsoft.com/en-us/library/cc232094.aspx */
+ Stream_Read_UINT64(input, liCreationTime.QuadPart);
+ Stream_Read_UINT64(input, liLastAccessTime.QuadPart);
+ Stream_Read_UINT64(input, liLastWriteTime.QuadPart);
+ Stream_Read_UINT64(input, liChangeTime.QuadPart);
+ Stream_Read_UINT32(input, FileAttributes);
+
+ if (!PathFileExistsW(file->fullpath))
+ return FALSE;
+
+ if (file->file_handle == INVALID_HANDLE_VALUE)
+ {
+ WLog_ERR(TAG, "Unable to set file time %s (%" PRId32 ")", file->fullpath,
+ GetLastError());
+ return FALSE;
+ }
+
+ if (liCreationTime.QuadPart != 0)
+ {
+ ftCreationTime.dwHighDateTime = liCreationTime.u.HighPart;
+ ftCreationTime.dwLowDateTime = liCreationTime.u.LowPart;
+ pftCreationTime = &ftCreationTime;
+ }
+
+ if (liLastAccessTime.QuadPart != 0)
+ {
+ ftLastAccessTime.dwHighDateTime = liLastAccessTime.u.HighPart;
+ ftLastAccessTime.dwLowDateTime = liLastAccessTime.u.LowPart;
+ pftLastAccessTime = &ftLastAccessTime;
+ }
+
+ if (liLastWriteTime.QuadPart != 0)
+ {
+ ftLastWriteTime.dwHighDateTime = liLastWriteTime.u.HighPart;
+ ftLastWriteTime.dwLowDateTime = liLastWriteTime.u.LowPart;
+ pftLastWriteTime = &ftLastWriteTime;
+ }
+
+ if (liChangeTime.QuadPart != 0 && liChangeTime.QuadPart > liLastWriteTime.QuadPart)
+ {
+ ftLastWriteTime.dwHighDateTime = liChangeTime.u.HighPart;
+ ftLastWriteTime.dwLowDateTime = liChangeTime.u.LowPart;
+ pftLastWriteTime = &ftLastWriteTime;
+ }
+
+ DEBUG_WSTR("SetFileTime %s", file->fullpath);
+
+ SetFileAttributesW(file->fullpath, FileAttributes);
+ if (!SetFileTime(file->file_handle, pftCreationTime, pftLastAccessTime,
+ pftLastWriteTime))
+ {
+ WLog_ERR(TAG, "Unable to set file time to %s", file->fullpath);
+ return FALSE;
+ }
+
+ break;
+
+ case FileEndOfFileInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232067.aspx */
+ case FileAllocationInformation:
+ if (!Stream_CheckAndLogRequiredLength(TAG, input, 8))
+ return FALSE;
+
+ /* http://msdn.microsoft.com/en-us/library/cc232076.aspx */
+ Stream_Read_INT64(input, size);
+
+ if (file->file_handle == INVALID_HANDLE_VALUE)
+ {
+ WLog_ERR(TAG, "Unable to truncate %s to %" PRId64 " (%" PRId32 ")", file->fullpath,
+ size, GetLastError());
+ return FALSE;
+ }
+
+ liSize.QuadPart = size;
+
+ if (!SetFilePointerEx(file->file_handle, liSize, NULL, FILE_BEGIN))
+ {
+ WLog_ERR(TAG, "Unable to truncate %s to %" PRId64 " (%" PRId32 ")", file->fullpath,
+ size, GetLastError());
+ return FALSE;
+ }
+
+ DEBUG_WSTR("Truncate %s", file->fullpath);
+
+ if (SetEndOfFile(file->file_handle) == 0)
+ {
+ WLog_ERR(TAG, "Unable to truncate %s to %" PRId64 " (%" PRId32 ")", file->fullpath,
+ size, GetLastError());
+ return FALSE;
+ }
+
+ break;
+
+ case FileDispositionInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232098.aspx */
+ /* http://msdn.microsoft.com/en-us/library/cc241371.aspx */
+ if (file->is_dir && !PathIsDirectoryEmptyW(file->fullpath))
+ break; /* TODO: SetLastError ??? */
+
+ if (Length)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, input, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(input, delete_pending);
+ }
+ else
+ delete_pending = 1;
+
+ if (delete_pending)
+ {
+ DEBUG_WSTR("SetDeletePending %s", file->fullpath);
+ attr = GetFileAttributesW(file->fullpath);
+
+ if (attr & FILE_ATTRIBUTE_READONLY)
+ {
+ SetLastError(ERROR_ACCESS_DENIED);
+ return FALSE;
+ }
+ }
+
+ file->delete_pending = delete_pending;
+ break;
+
+ case FileRenameInformation:
+ if (!Stream_CheckAndLogRequiredLength(TAG, input, 6))
+ return FALSE;
+
+ /* http://msdn.microsoft.com/en-us/library/cc232085.aspx */
+ Stream_Read_UINT8(input, ReplaceIfExists);
+ Stream_Seek_UINT8(input); /* RootDirectory */
+ Stream_Read_UINT32(input, FileNameLength);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, input, FileNameLength))
+ return FALSE;
+
+ fullpath = drive_file_combine_fullpath(file->basepath, Stream_ConstPointer(input),
+ FileNameLength / sizeof(WCHAR));
+
+ if (!fullpath)
+ return FALSE;
+
+#ifdef _WIN32
+
+ if (file->file_handle != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(file->file_handle);
+ file->file_handle = INVALID_HANDLE_VALUE;
+ }
+
+#endif
+ DEBUG_WSTR("MoveFileExW %s", file->fullpath);
+
+ if (MoveFileExW(file->fullpath, fullpath,
+ MOVEFILE_COPY_ALLOWED |
+ (ReplaceIfExists ? MOVEFILE_REPLACE_EXISTING : 0)))
+ {
+ if (!drive_file_set_fullpath(file, fullpath))
+ return FALSE;
+ }
+ else
+ {
+ free(fullpath);
+ return FALSE;
+ }
+
+#ifdef _WIN32
+ drive_file_init(file);
+#endif
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL drive_file_query_directory(DRIVE_FILE* file, UINT32 FsInformationClass, BYTE InitialQuery,
+ const WCHAR* path, UINT32 PathWCharLength, wStream* output)
+{
+ size_t length = 0;
+ WCHAR* ent_path = NULL;
+
+ if (!file || !path || !output)
+ return FALSE;
+
+ if (InitialQuery != 0)
+ {
+ /* release search handle */
+ if (file->find_handle != INVALID_HANDLE_VALUE)
+ FindClose(file->find_handle);
+
+ ent_path = drive_file_combine_fullpath(file->basepath, path, PathWCharLength);
+ /* open new search handle and retrieve the first entry */
+ file->find_handle = FindFirstFileW(ent_path, &file->find_data);
+ free(ent_path);
+
+ if (file->find_handle == INVALID_HANDLE_VALUE)
+ goto out_fail;
+ }
+ else if (!FindNextFileW(file->find_handle, &file->find_data))
+ goto out_fail;
+
+ length = _wcslen(file->find_data.cFileName) * 2;
+
+ switch (FsInformationClass)
+ {
+ case FileDirectoryInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232097.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 64 + length))
+ goto out_fail;
+
+ if (length > UINT32_MAX - 64)
+ goto out_fail;
+
+ Stream_Write_UINT32(output, (UINT32)(64 + length)); /* Length */
+ Stream_Write_UINT32(output, 0); /* NextEntryOffset */
+ Stream_Write_UINT32(output, 0); /* FileIndex */
+ Stream_Write_UINT32(output,
+ file->find_data.ftCreationTime.dwLowDateTime); /* CreationTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftCreationTime.dwHighDateTime); /* CreationTime */
+ Stream_Write_UINT32(
+ output, file->find_data.ftLastAccessTime.dwLowDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(
+ output, file->find_data.ftLastAccessTime.dwHighDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwLowDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwHighDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwLowDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwHighDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* EndOfFile */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* EndOfFile */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* AllocationSize */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* AllocationSize */
+ Stream_Write_UINT32(output, file->find_data.dwFileAttributes); /* FileAttributes */
+ Stream_Write_UINT32(output, (UINT32)length); /* FileNameLength */
+ Stream_Write(output, file->find_data.cFileName, length);
+ break;
+
+ case FileFullDirectoryInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232068.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 68 + length))
+ goto out_fail;
+
+ if (length > UINT32_MAX - 68)
+ goto out_fail;
+
+ Stream_Write_UINT32(output, (UINT32)(68 + length)); /* Length */
+ Stream_Write_UINT32(output, 0); /* NextEntryOffset */
+ Stream_Write_UINT32(output, 0); /* FileIndex */
+ Stream_Write_UINT32(output,
+ file->find_data.ftCreationTime.dwLowDateTime); /* CreationTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftCreationTime.dwHighDateTime); /* CreationTime */
+ Stream_Write_UINT32(
+ output, file->find_data.ftLastAccessTime.dwLowDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(
+ output, file->find_data.ftLastAccessTime.dwHighDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwLowDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwHighDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwLowDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwHighDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* EndOfFile */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* EndOfFile */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* AllocationSize */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* AllocationSize */
+ Stream_Write_UINT32(output, file->find_data.dwFileAttributes); /* FileAttributes */
+ Stream_Write_UINT32(output, (UINT32)length); /* FileNameLength */
+ Stream_Write_UINT32(output, 0); /* EaSize */
+ Stream_Write(output, file->find_data.cFileName, length);
+ break;
+
+ case FileBothDirectoryInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232095.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 93 + length))
+ goto out_fail;
+
+ if (length > UINT32_MAX - 93)
+ goto out_fail;
+
+ Stream_Write_UINT32(output, (UINT32)(93 + length)); /* Length */
+ Stream_Write_UINT32(output, 0); /* NextEntryOffset */
+ Stream_Write_UINT32(output, 0); /* FileIndex */
+ Stream_Write_UINT32(output,
+ file->find_data.ftCreationTime.dwLowDateTime); /* CreationTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftCreationTime.dwHighDateTime); /* CreationTime */
+ Stream_Write_UINT32(
+ output, file->find_data.ftLastAccessTime.dwLowDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(
+ output, file->find_data.ftLastAccessTime.dwHighDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwLowDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwHighDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwLowDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwHighDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* EndOfFile */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* EndOfFile */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* AllocationSize */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* AllocationSize */
+ Stream_Write_UINT32(output, file->find_data.dwFileAttributes); /* FileAttributes */
+ Stream_Write_UINT32(output, (UINT32)length); /* FileNameLength */
+ Stream_Write_UINT32(output, 0); /* EaSize */
+ Stream_Write_UINT8(output, 0); /* ShortNameLength */
+ /* Reserved(1), MUST NOT be added! */
+ Stream_Zero(output, 24); /* ShortName */
+ Stream_Write(output, file->find_data.cFileName, length);
+ break;
+
+ case FileNamesInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232077.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 12 + length))
+ goto out_fail;
+
+ if (length > UINT32_MAX - 12)
+ goto out_fail;
+
+ Stream_Write_UINT32(output, (UINT32)(12 + length)); /* Length */
+ Stream_Write_UINT32(output, 0); /* NextEntryOffset */
+ Stream_Write_UINT32(output, 0); /* FileIndex */
+ Stream_Write_UINT32(output, (UINT32)length); /* FileNameLength */
+ Stream_Write(output, file->find_data.cFileName, length);
+ break;
+
+ default:
+ WLog_ERR(TAG, "unhandled FsInformationClass %" PRIu32, FsInformationClass);
+ /* Unhandled FsInformationClass */
+ goto out_fail;
+ }
+
+ return TRUE;
+out_fail:
+ Stream_Write_UINT32(output, 0); /* Length */
+ Stream_Write_UINT8(output, 0); /* Padding */
+ return FALSE;
+}
diff --git a/channels/drive/client/drive_file.h b/channels/drive/client/drive_file.h
new file mode 100644
index 0000000..761295b
--- /dev/null
+++ b/channels/drive/client/drive_file.h
@@ -0,0 +1,67 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * File System Virtual Channel
+ *
+ * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2012 Gerald Richter
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 Inuvika Inc.
+ * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_DRIVE_CLIENT_FILE_H
+#define FREERDP_CHANNEL_DRIVE_CLIENT_FILE_H
+
+#include <winpr/stream.h>
+#include <winpr/file.h>
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("drive.client")
+
+typedef struct
+{
+ UINT32 id;
+ BOOL is_dir;
+ HANDLE file_handle;
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ const WCHAR* basepath;
+ WCHAR* fullpath;
+ BOOL delete_pending;
+ UINT32 FileAttributes;
+ UINT32 SharedAccess;
+ UINT32 DesiredAccess;
+ UINT32 CreateDisposition;
+ UINT32 CreateOptions;
+} DRIVE_FILE;
+
+DRIVE_FILE* drive_file_new(const WCHAR* base_path, const WCHAR* path, UINT32 PathWCharLength,
+ UINT32 id, UINT32 DesiredAccess, UINT32 CreateDisposition,
+ UINT32 CreateOptions, UINT32 FileAttributes, UINT32 SharedAccess);
+BOOL drive_file_free(DRIVE_FILE* file);
+
+BOOL drive_file_open(DRIVE_FILE* file);
+BOOL drive_file_seek(DRIVE_FILE* file, UINT64 Offset);
+BOOL drive_file_read(DRIVE_FILE* file, BYTE* buffer, UINT32* Length);
+BOOL drive_file_write(DRIVE_FILE* file, const BYTE* buffer, UINT32 Length);
+BOOL drive_file_query_information(DRIVE_FILE* file, UINT32 FsInformationClass, wStream* output);
+BOOL drive_file_set_information(DRIVE_FILE* file, UINT32 FsInformationClass, UINT32 Length,
+ wStream* input);
+BOOL drive_file_query_directory(DRIVE_FILE* file, UINT32 FsInformationClass, BYTE InitialQuery,
+ const WCHAR* path, UINT32 PathWCharLength, wStream* output);
+
+#endif /* FREERDP_CHANNEL_DRIVE_FILE_H */
diff --git a/channels/drive/client/drive_main.c b/channels/drive/client/drive_main.c
new file mode 100644
index 0000000..0fdc2e0
--- /dev/null
+++ b/channels/drive/client/drive_main.c
@@ -0,0 +1,1120 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * File System Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/path.h>
+#include <winpr/file.h>
+#include <winpr/string.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/environment.h>
+#include <winpr/interlocked.h>
+#include <winpr/collections.h>
+#include <winpr/shell.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/rdpdr.h>
+
+#include "drive_file.h"
+
+typedef struct
+{
+ DEVICE device;
+
+ WCHAR* path;
+ BOOL automount;
+ UINT32 PathLength;
+ wListDictionary* files;
+
+ HANDLE thread;
+ wMessageQueue* IrpQueue;
+
+ DEVMAN* devman;
+
+ rdpContext* rdpcontext;
+} DRIVE_DEVICE;
+
+static DWORD drive_map_windows_err(DWORD fs_errno)
+{
+ DWORD rc = 0;
+
+ /* try to return NTSTATUS version of error code */
+
+ switch (fs_errno)
+ {
+ case STATUS_SUCCESS:
+ rc = STATUS_SUCCESS;
+ break;
+
+ case ERROR_ACCESS_DENIED:
+ case ERROR_SHARING_VIOLATION:
+ rc = STATUS_ACCESS_DENIED;
+ break;
+
+ case ERROR_FILE_NOT_FOUND:
+ rc = STATUS_NO_SUCH_FILE;
+ break;
+
+ case ERROR_BUSY_DRIVE:
+ rc = STATUS_DEVICE_BUSY;
+ break;
+
+ case ERROR_INVALID_DRIVE:
+ rc = STATUS_NO_SUCH_DEVICE;
+ break;
+
+ case ERROR_NOT_READY:
+ rc = STATUS_NO_SUCH_DEVICE;
+ break;
+
+ case ERROR_FILE_EXISTS:
+ case ERROR_ALREADY_EXISTS:
+ rc = STATUS_OBJECT_NAME_COLLISION;
+ break;
+
+ case ERROR_INVALID_NAME:
+ rc = STATUS_NO_SUCH_FILE;
+ break;
+
+ case ERROR_INVALID_HANDLE:
+ rc = STATUS_INVALID_HANDLE;
+ break;
+
+ case ERROR_NO_MORE_FILES:
+ rc = STATUS_NO_MORE_FILES;
+ break;
+
+ case ERROR_DIRECTORY:
+ rc = STATUS_NOT_A_DIRECTORY;
+ break;
+
+ case ERROR_PATH_NOT_FOUND:
+ rc = STATUS_OBJECT_PATH_NOT_FOUND;
+ break;
+
+ default:
+ rc = STATUS_UNSUCCESSFUL;
+ WLog_ERR(TAG, "Error code not found: %" PRIu32 "", fs_errno);
+ break;
+ }
+
+ return rc;
+}
+
+static DRIVE_FILE* drive_get_file_by_id(DRIVE_DEVICE* drive, UINT32 id)
+{
+ DRIVE_FILE* file = NULL;
+ void* key = (void*)(size_t)id;
+
+ if (!drive)
+ return NULL;
+
+ file = (DRIVE_FILE*)ListDictionary_GetItemValue(drive->files, key);
+ return file;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_create(DRIVE_DEVICE* drive, IRP* irp)
+{
+ UINT32 FileId = 0;
+ DRIVE_FILE* file = NULL;
+ BYTE Information = 0;
+ UINT32 FileAttributes = 0;
+ UINT32 SharedAccess = 0;
+ UINT32 DesiredAccess = 0;
+ UINT32 CreateDisposition = 0;
+ UINT32 CreateOptions = 0;
+ UINT32 PathLength = 0;
+ UINT64 allocationSize = 0;
+ const WCHAR* path = NULL;
+
+ if (!drive || !irp || !irp->devman || !irp->Complete)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 6 * 4 + 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, DesiredAccess);
+ Stream_Read_UINT64(irp->input, allocationSize);
+ Stream_Read_UINT32(irp->input, FileAttributes);
+ Stream_Read_UINT32(irp->input, SharedAccess);
+ Stream_Read_UINT32(irp->input, CreateDisposition);
+ Stream_Read_UINT32(irp->input, CreateOptions);
+ Stream_Read_UINT32(irp->input, PathLength);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, PathLength))
+ return ERROR_INVALID_DATA;
+
+ path = Stream_ConstPointer(irp->input);
+ FileId = irp->devman->id_sequence++;
+ file = drive_file_new(drive->path, path, PathLength / sizeof(WCHAR), FileId, DesiredAccess,
+ CreateDisposition, CreateOptions, FileAttributes, SharedAccess);
+
+ if (!file)
+ {
+ irp->IoStatus = drive_map_windows_err(GetLastError());
+ FileId = 0;
+ Information = 0;
+ }
+ else
+ {
+ void* key = (void*)(size_t)file->id;
+
+ if (!ListDictionary_Add(drive->files, key, file))
+ {
+ WLog_ERR(TAG, "ListDictionary_Add failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ switch (CreateDisposition)
+ {
+ case FILE_SUPERSEDE:
+ case FILE_OPEN:
+ case FILE_CREATE:
+ case FILE_OVERWRITE:
+ Information = FILE_SUPERSEDED;
+ break;
+
+ case FILE_OPEN_IF:
+ Information = FILE_OPENED;
+ break;
+
+ case FILE_OVERWRITE_IF:
+ Information = FILE_OVERWRITTEN;
+ break;
+
+ default:
+ Information = 0;
+ break;
+ }
+ }
+
+ Stream_Write_UINT32(irp->output, FileId);
+ Stream_Write_UINT8(irp->output, Information);
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_close(DRIVE_DEVICE* drive, IRP* irp)
+{
+ void* key = NULL;
+ DRIVE_FILE* file = NULL;
+
+ if (!drive || !irp || !irp->Complete || !irp->output)
+ return ERROR_INVALID_PARAMETER;
+
+ file = drive_get_file_by_id(drive, irp->FileId);
+ key = (void*)(size_t)irp->FileId;
+
+ if (!file)
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ else
+ {
+ ListDictionary_Take(drive->files, key);
+
+ if (drive_file_free(file))
+ irp->IoStatus = STATUS_SUCCESS;
+ else
+ irp->IoStatus = drive_map_windows_err(GetLastError());
+ }
+
+ Stream_Zero(irp->output, 5); /* Padding(5) */
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_read(DRIVE_DEVICE* drive, IRP* irp)
+{
+ DRIVE_FILE* file = NULL;
+ UINT32 Length = 0;
+ UINT64 Offset = 0;
+
+ if (!drive || !irp || !irp->output || !irp->Complete)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, Length);
+ Stream_Read_UINT64(irp->input, Offset);
+ file = drive_get_file_by_id(drive, irp->FileId);
+
+ if (!file)
+ {
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ Length = 0;
+ }
+ else if (!drive_file_seek(file, Offset))
+ {
+ irp->IoStatus = drive_map_windows_err(GetLastError());
+ Length = 0;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(irp->output, Length + 4))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+ else if (Length == 0)
+ Stream_Write_UINT32(irp->output, 0);
+ else
+ {
+ BYTE* buffer = Stream_PointerAs(irp->output, BYTE) + sizeof(UINT32);
+
+ if (!drive_file_read(file, buffer, &Length))
+ {
+ irp->IoStatus = drive_map_windows_err(GetLastError());
+ Stream_Write_UINT32(irp->output, 0);
+ }
+ else
+ {
+ Stream_Write_UINT32(irp->output, Length);
+ Stream_Seek(irp->output, Length);
+ }
+ }
+
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_write(DRIVE_DEVICE* drive, IRP* irp)
+{
+ DRIVE_FILE* file = NULL;
+ UINT32 Length = 0;
+ UINT64 Offset = 0;
+
+ if (!drive || !irp || !irp->input || !irp->output || !irp->Complete)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, Length);
+ Stream_Read_UINT64(irp->input, Offset);
+ Stream_Seek(irp->input, 20); /* Padding */
+ const void* ptr = Stream_ConstPointer(irp->input);
+ if (!Stream_SafeSeek(irp->input, Length))
+ return ERROR_INVALID_DATA;
+ file = drive_get_file_by_id(drive, irp->FileId);
+
+ if (!file)
+ {
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ Length = 0;
+ }
+ else if (!drive_file_seek(file, Offset))
+ {
+ irp->IoStatus = drive_map_windows_err(GetLastError());
+ Length = 0;
+ }
+ else if (!drive_file_write(file, ptr, Length))
+ {
+ irp->IoStatus = drive_map_windows_err(GetLastError());
+ Length = 0;
+ }
+
+ Stream_Write_UINT32(irp->output, Length);
+ Stream_Write_UINT8(irp->output, 0); /* Padding */
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_query_information(DRIVE_DEVICE* drive, IRP* irp)
+{
+ DRIVE_FILE* file = NULL;
+ UINT32 FsInformationClass = 0;
+
+ if (!drive || !irp || !irp->Complete)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, FsInformationClass);
+ file = drive_get_file_by_id(drive, irp->FileId);
+
+ if (!file)
+ {
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ }
+ else if (!drive_file_query_information(file, FsInformationClass, irp->output))
+ {
+ irp->IoStatus = drive_map_windows_err(GetLastError());
+ }
+
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_set_information(DRIVE_DEVICE* drive, IRP* irp)
+{
+ DRIVE_FILE* file = NULL;
+ UINT32 FsInformationClass = 0;
+ UINT32 Length = 0;
+
+ if (!drive || !irp || !irp->Complete || !irp->input || !irp->output)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, FsInformationClass);
+ Stream_Read_UINT32(irp->input, Length);
+ Stream_Seek(irp->input, 24); /* Padding */
+ file = drive_get_file_by_id(drive, irp->FileId);
+
+ if (!file)
+ {
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ }
+ else if (!drive_file_set_information(file, FsInformationClass, Length, irp->input))
+ {
+ irp->IoStatus = drive_map_windows_err(GetLastError());
+ }
+
+ if (file && file->is_dir && !PathIsDirectoryEmptyW(file->fullpath))
+ irp->IoStatus = STATUS_DIRECTORY_NOT_EMPTY;
+
+ Stream_Write_UINT32(irp->output, Length);
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_query_volume_information(DRIVE_DEVICE* drive, IRP* irp)
+{
+ UINT32 FsInformationClass = 0;
+ wStream* output = NULL;
+ DWORD lpSectorsPerCluster = 0;
+ DWORD lpBytesPerSector = 0;
+ DWORD lpNumberOfFreeClusters = 0;
+ DWORD lpTotalNumberOfClusters = 0;
+ WIN32_FILE_ATTRIBUTE_DATA wfad = { 0 };
+ WCHAR LabelBuffer[32] = { 0 };
+
+ if (!drive || !irp)
+ return ERROR_INVALID_PARAMETER;
+
+ output = irp->output;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, FsInformationClass);
+ GetDiskFreeSpaceW(drive->path, &lpSectorsPerCluster, &lpBytesPerSector, &lpNumberOfFreeClusters,
+ &lpTotalNumberOfClusters);
+
+ switch (FsInformationClass)
+ {
+ case FileFsVolumeInformation:
+ {
+ /* http://msdn.microsoft.com/en-us/library/cc232108.aspx */
+ const WCHAR* volumeLabel =
+ InitializeConstWCharFromUtf8("FREERDP", LabelBuffer, ARRAYSIZE(LabelBuffer));
+ const size_t volumeLabelLen = (_wcslen(volumeLabel) + 1) * sizeof(WCHAR);
+ const size_t length = 17ul + volumeLabelLen;
+
+ Stream_Write_UINT32(output, length); /* Length */
+
+ if (!Stream_EnsureRemainingCapacity(output, length))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ GetFileAttributesExW(drive->path, GetFileExInfoStandard, &wfad);
+ Stream_Write_UINT32(output, wfad.ftCreationTime.dwLowDateTime); /* VolumeCreationTime */
+ Stream_Write_UINT32(output,
+ wfad.ftCreationTime.dwHighDateTime); /* VolumeCreationTime */
+ Stream_Write_UINT32(output, lpNumberOfFreeClusters & 0xffff); /* VolumeSerialNumber */
+ Stream_Write_UINT32(output, volumeLabelLen); /* VolumeLabelLength */
+ Stream_Write_UINT8(output, 0); /* SupportsObjects */
+ /* Reserved(1), MUST NOT be added! */
+ Stream_Write(output, volumeLabel, volumeLabelLen); /* VolumeLabel (Unicode) */
+ }
+ break;
+
+ case FileFsSizeInformation:
+ /* http://msdn.microsoft.com/en-us/library/cc232107.aspx */
+ Stream_Write_UINT32(output, 24); /* Length */
+
+ if (!Stream_EnsureRemainingCapacity(output, 24))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT64(output, lpTotalNumberOfClusters); /* TotalAllocationUnits */
+ Stream_Write_UINT64(output, lpNumberOfFreeClusters); /* AvailableAllocationUnits */
+ Stream_Write_UINT32(output, lpSectorsPerCluster); /* SectorsPerAllocationUnit */
+ Stream_Write_UINT32(output, lpBytesPerSector); /* BytesPerSector */
+ break;
+
+ case FileFsAttributeInformation:
+ {
+ /* http://msdn.microsoft.com/en-us/library/cc232101.aspx */
+ const WCHAR* diskType =
+ InitializeConstWCharFromUtf8("FAT32", LabelBuffer, ARRAYSIZE(LabelBuffer));
+ const size_t diskTypeLen = (wcslen(diskType) + 1) * sizeof(WCHAR);
+ const size_t length = 12ul + diskTypeLen;
+ Stream_Write_UINT32(output, length); /* Length */
+
+ if (!Stream_EnsureRemainingCapacity(output, length))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT32(output, FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES |
+ FILE_UNICODE_ON_DISK); /* FileSystemAttributes */
+ Stream_Write_UINT32(output, MAX_PATH); /* MaximumComponentNameLength */
+ Stream_Write_UINT32(output, diskTypeLen); /* FileSystemNameLength */
+ Stream_Write(output, diskType, diskTypeLen); /* FileSystemName (Unicode) */
+ }
+ break;
+
+ case FileFsFullSizeInformation:
+ /* http://msdn.microsoft.com/en-us/library/cc232104.aspx */
+ Stream_Write_UINT32(output, 32); /* Length */
+
+ if (!Stream_EnsureRemainingCapacity(output, 32))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT64(output, lpTotalNumberOfClusters); /* TotalAllocationUnits */
+ Stream_Write_UINT64(output,
+ lpNumberOfFreeClusters); /* CallerAvailableAllocationUnits */
+ Stream_Write_UINT64(output, lpNumberOfFreeClusters); /* AvailableAllocationUnits */
+ Stream_Write_UINT32(output, lpSectorsPerCluster); /* SectorsPerAllocationUnit */
+ Stream_Write_UINT32(output, lpBytesPerSector); /* BytesPerSector */
+ break;
+
+ case FileFsDeviceInformation:
+ /* http://msdn.microsoft.com/en-us/library/cc232109.aspx */
+ Stream_Write_UINT32(output, 8); /* Length */
+
+ if (!Stream_EnsureRemainingCapacity(output, 8))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT32(output, FILE_DEVICE_DISK); /* DeviceType */
+ Stream_Write_UINT32(output, 0); /* Characteristics */
+ break;
+
+ default:
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ Stream_Write_UINT32(output, 0); /* Length */
+ break;
+ }
+
+ return irp->Complete(irp);
+}
+
+/* http://msdn.microsoft.com/en-us/library/cc241518.aspx */
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_silent_ignore(DRIVE_DEVICE* drive, IRP* irp)
+{
+ UINT32 FsInformationClass = 0;
+
+ if (!drive || !irp || !irp->output || !irp->Complete)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, FsInformationClass);
+ Stream_Write_UINT32(irp->output, 0); /* Length */
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_query_directory(DRIVE_DEVICE* drive, IRP* irp)
+{
+ const WCHAR* path = NULL;
+ DRIVE_FILE* file = NULL;
+ BYTE InitialQuery = 0;
+ UINT32 PathLength = 0;
+ UINT32 FsInformationClass = 0;
+
+ if (!drive || !irp || !irp->Complete)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, FsInformationClass);
+ Stream_Read_UINT8(irp->input, InitialQuery);
+ Stream_Read_UINT32(irp->input, PathLength);
+ Stream_Seek(irp->input, 23); /* Padding */
+ path = Stream_ConstPointer(irp->input);
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, PathLength))
+ return ERROR_INVALID_DATA;
+
+ file = drive_get_file_by_id(drive, irp->FileId);
+
+ if (file == NULL)
+ {
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ Stream_Write_UINT32(irp->output, 0); /* Length */
+ }
+ else if (!drive_file_query_directory(file, FsInformationClass, InitialQuery, path,
+ PathLength / sizeof(WCHAR), irp->output))
+ {
+ irp->IoStatus = drive_map_windows_err(GetLastError());
+ }
+
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_directory_control(DRIVE_DEVICE* drive, IRP* irp)
+{
+ if (!drive || !irp)
+ return ERROR_INVALID_PARAMETER;
+
+ switch (irp->MinorFunction)
+ {
+ case IRP_MN_QUERY_DIRECTORY:
+ return drive_process_irp_query_directory(drive, irp);
+
+ case IRP_MN_NOTIFY_CHANGE_DIRECTORY: /* TODO */
+ return irp->Discard(irp);
+
+ default:
+ irp->IoStatus = STATUS_NOT_SUPPORTED;
+ Stream_Write_UINT32(irp->output, 0); /* Length */
+ return irp->Complete(irp);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_device_control(DRIVE_DEVICE* drive, IRP* irp)
+{
+ if (!drive || !irp)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(irp->output, 0); /* OutputBufferLength */
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp(DRIVE_DEVICE* drive, IRP* irp)
+{
+ UINT error = 0;
+
+ if (!drive || !irp)
+ return ERROR_INVALID_PARAMETER;
+
+ irp->IoStatus = STATUS_SUCCESS;
+
+ switch (irp->MajorFunction)
+ {
+ case IRP_MJ_CREATE:
+ error = drive_process_irp_create(drive, irp);
+ break;
+
+ case IRP_MJ_CLOSE:
+ error = drive_process_irp_close(drive, irp);
+ break;
+
+ case IRP_MJ_READ:
+ error = drive_process_irp_read(drive, irp);
+ break;
+
+ case IRP_MJ_WRITE:
+ error = drive_process_irp_write(drive, irp);
+ break;
+
+ case IRP_MJ_QUERY_INFORMATION:
+ error = drive_process_irp_query_information(drive, irp);
+ break;
+
+ case IRP_MJ_SET_INFORMATION:
+ error = drive_process_irp_set_information(drive, irp);
+ break;
+
+ case IRP_MJ_QUERY_VOLUME_INFORMATION:
+ error = drive_process_irp_query_volume_information(drive, irp);
+ break;
+
+ case IRP_MJ_LOCK_CONTROL:
+ error = drive_process_irp_silent_ignore(drive, irp);
+ break;
+
+ case IRP_MJ_DIRECTORY_CONTROL:
+ error = drive_process_irp_directory_control(drive, irp);
+ break;
+
+ case IRP_MJ_DEVICE_CONTROL:
+ error = drive_process_irp_device_control(drive, irp);
+ break;
+
+ default:
+ irp->IoStatus = STATUS_NOT_SUPPORTED;
+ error = irp->Complete(irp);
+ break;
+ }
+
+ return error;
+}
+
+static DWORD WINAPI drive_thread_func(LPVOID arg)
+{
+ IRP* irp = NULL;
+ wMessage message = { 0 };
+ DRIVE_DEVICE* drive = (DRIVE_DEVICE*)arg;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!drive)
+ {
+ error = ERROR_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ while (1)
+ {
+ if (!MessageQueue_Wait(drive->IrpQueue))
+ {
+ WLog_ERR(TAG, "MessageQueue_Wait failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (!MessageQueue_Peek(drive->IrpQueue, &message, TRUE))
+ {
+ WLog_ERR(TAG, "MessageQueue_Peek failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (message.id == WMQ_QUIT)
+ break;
+
+ irp = (IRP*)message.wParam;
+
+ if (irp)
+ {
+ if ((error = drive_process_irp(drive, irp)))
+ {
+ WLog_ERR(TAG, "drive_process_irp failed with error %" PRIu32 "!", error);
+ break;
+ }
+ }
+ }
+
+fail:
+
+ if (error && drive && drive->rdpcontext)
+ setChannelError(drive->rdpcontext, error, "drive_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_irp_request(DEVICE* device, IRP* irp)
+{
+ DRIVE_DEVICE* drive = (DRIVE_DEVICE*)device;
+
+ if (!drive)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!MessageQueue_Post(drive->IrpQueue, NULL, 0, (void*)irp, NULL))
+ {
+ WLog_ERR(TAG, "MessageQueue_Post failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT drive_free_int(DRIVE_DEVICE* drive)
+{
+ UINT error = CHANNEL_RC_OK;
+
+ if (!drive)
+ return ERROR_INVALID_PARAMETER;
+
+ CloseHandle(drive->thread);
+ ListDictionary_Free(drive->files);
+ MessageQueue_Free(drive->IrpQueue);
+ Stream_Free(drive->device.data, TRUE);
+ free(drive->path);
+ free(drive);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_free(DEVICE* device)
+{
+ DRIVE_DEVICE* drive = (DRIVE_DEVICE*)device;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!drive)
+ return ERROR_INVALID_PARAMETER;
+
+ if (MessageQueue_PostQuit(drive->IrpQueue, 0) &&
+ (WaitForSingleObject(drive->thread, INFINITE) == WAIT_FAILED))
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ return drive_free_int(drive);
+}
+
+/**
+ * Helper function used for freeing list dictionary value object
+ */
+static void drive_file_objfree(void* obj)
+{
+ drive_file_free((DRIVE_FILE*)obj);
+}
+
+static void drive_message_free(void* obj)
+{
+ wMessage* msg = obj;
+ if (!msg)
+ return;
+ if (msg->id != 0)
+ return;
+
+ IRP* irp = (IRP*)msg->wParam;
+ if (!irp)
+ return;
+ WINPR_ASSERT(irp->Discard);
+ irp->Discard(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_register_drive_path(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints, const char* name,
+ const char* path, BOOL automount)
+{
+ size_t length = 0;
+ DRIVE_DEVICE* drive = NULL;
+ UINT error = ERROR_INTERNAL_ERROR;
+
+ if (!pEntryPoints || !name || !path)
+ {
+ WLog_ERR(TAG, "[%s] Invalid parameters: pEntryPoints=%p, name=%p, path=%p", pEntryPoints,
+ name, path);
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ if (name[0] && path[0])
+ {
+ size_t pathLength = strnlen(path, MAX_PATH);
+ drive = (DRIVE_DEVICE*)calloc(1, sizeof(DRIVE_DEVICE));
+
+ if (!drive)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ drive->device.type = RDPDR_DTYP_FILESYSTEM;
+ drive->device.IRPRequest = drive_irp_request;
+ drive->device.Free = drive_free;
+ drive->rdpcontext = pEntryPoints->rdpcontext;
+ drive->automount = automount;
+ length = strlen(name);
+ drive->device.data = Stream_New(NULL, length + 1);
+
+ if (!drive->device.data)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out_error;
+ }
+
+ for (size_t i = 0; i < length; i++)
+ {
+ /* Filter 2.2.1.3 Device Announce Header (DEVICE_ANNOUNCE) forbidden symbols */
+ switch (name[i])
+ {
+ case ':':
+ case '<':
+ case '>':
+ case '\"':
+ case '/':
+ case '\\':
+ case '|':
+ case ' ':
+ Stream_Write_UINT8(drive->device.data, '_');
+ break;
+ default:
+ Stream_Write_UINT8(drive->device.data, (BYTE)name[i]);
+ break;
+ }
+ }
+ Stream_Write_UINT8(drive->device.data, '\0');
+
+ drive->device.name = (const char*)Stream_Buffer(drive->device.data);
+ if (!drive->device.name)
+ goto out_error;
+
+ if ((pathLength > 1) && (path[pathLength - 1] == '/'))
+ pathLength--;
+
+ drive->path = ConvertUtf8NToWCharAlloc(path, pathLength, NULL);
+ if (!drive->path)
+ {
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out_error;
+ }
+
+ drive->files = ListDictionary_New(TRUE);
+
+ if (!drive->files)
+ {
+ WLog_ERR(TAG, "ListDictionary_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out_error;
+ }
+
+ ListDictionary_ValueObject(drive->files)->fnObjectFree = drive_file_objfree;
+ drive->IrpQueue = MessageQueue_New(NULL);
+
+ if (!drive->IrpQueue)
+ {
+ WLog_ERR(TAG, "ListDictionary_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out_error;
+ }
+
+ wObject* obj = MessageQueue_Object(drive->IrpQueue);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = drive_message_free;
+
+ if ((error = pEntryPoints->RegisterDevice(pEntryPoints->devman, (DEVICE*)drive)))
+ {
+ WLog_ERR(TAG, "RegisterDevice failed with error %" PRIu32 "!", error);
+ goto out_error;
+ }
+
+ if (!(drive->thread =
+ CreateThread(NULL, 0, drive_thread_func, drive, CREATE_SUSPENDED, NULL)))
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ goto out_error;
+ }
+
+ ResumeThread(drive->thread);
+ }
+
+ return CHANNEL_RC_OK;
+out_error:
+ drive_free_int(drive);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT drive_DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints))
+{
+ RDPDR_DRIVE* drive = NULL;
+ UINT error = 0;
+#ifdef WIN32
+ int len;
+ char devlist[512], buf[512];
+ char* bufdup;
+ char* devdup;
+#endif
+
+ WINPR_ASSERT(pEntryPoints);
+
+ drive = (RDPDR_DRIVE*)pEntryPoints->device;
+ WINPR_ASSERT(drive);
+
+#ifndef WIN32
+ if (strcmp(drive->Path, "*") == 0)
+ {
+ /* all drives */
+ free(drive->Path);
+ drive->Path = _strdup("/");
+
+ if (!drive->Path)
+ {
+ WLog_ERR(TAG, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+ else if (strcmp(drive->Path, "%") == 0)
+ {
+ free(drive->Path);
+ drive->Path = GetKnownPath(KNOWN_PATH_HOME);
+
+ if (!drive->Path)
+ {
+ WLog_ERR(TAG, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+
+ error =
+ drive_register_drive_path(pEntryPoints, drive->device.Name, drive->Path, drive->automount);
+#else
+ /* Special case: path[0] == '*' -> export all drives */
+ /* Special case: path[0] == '%' -> user home dir */
+ if (strcmp(drive->Path, "%") == 0)
+ {
+ GetEnvironmentVariableA("USERPROFILE", buf, sizeof(buf));
+ PathCchAddBackslashA(buf, sizeof(buf));
+ free(drive->Path);
+ drive->Path = _strdup(buf);
+
+ if (!drive->Path)
+ {
+ WLog_ERR(TAG, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ error = drive_register_drive_path(pEntryPoints, drive->device.Name, drive->Path,
+ drive->automount);
+ }
+ else if (strcmp(drive->Path, "*") == 0)
+ {
+ /* Enumerate all devices: */
+ GetLogicalDriveStringsA(sizeof(devlist) - 1, devlist);
+
+ for (size_t i = 0;; i++)
+ {
+ char* dev = &devlist[i * 4];
+ if (!*dev)
+ break;
+ if (*dev > 'B')
+ {
+ /* Suppress disk drives A and B to avoid pesty messages */
+ len = sprintf_s(buf, sizeof(buf) - 4, "%s", drive->device.Name);
+ buf[len] = '_';
+ buf[len + 1] = dev[0];
+ buf[len + 2] = 0;
+ buf[len + 3] = 0;
+
+ if (!(bufdup = _strdup(buf)))
+ {
+ WLog_ERR(TAG, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if (!(devdup = _strdup(dev)))
+ {
+ WLog_ERR(TAG, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if ((error = drive_register_drive_path(pEntryPoints, bufdup, devdup, TRUE)))
+ {
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ error = drive_register_drive_path(pEntryPoints, drive->device.Name, drive->Path,
+ drive->automount);
+ }
+
+#endif
+ return error;
+}
diff --git a/channels/echo/CMakeLists.txt b/channels/echo/CMakeLists.txt
new file mode 100644
index 0000000..bfa8297
--- /dev/null
+++ b/channels/echo/CMakeLists.txt
@@ -0,0 +1,26 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("echo")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/echo/ChannelOptions.cmake b/channels/echo/ChannelOptions.cmake
new file mode 100644
index 0000000..eb4950a
--- /dev/null
+++ b/channels/echo/ChannelOptions.cmake
@@ -0,0 +1,13 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT ON)
+
+define_channel_options(NAME "echo" TYPE "dynamic"
+ DESCRIPTION "Echo Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPEECO]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/echo/client/CMakeLists.txt b/channels/echo/client/CMakeLists.txt
new file mode 100644
index 0000000..a66443d
--- /dev/null
+++ b/channels/echo/client/CMakeLists.txt
@@ -0,0 +1,30 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("echo")
+
+set(${MODULE_PREFIX}_SRCS
+ echo_main.c
+ echo_main.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+)
+include_directories(..)
+
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
diff --git a/channels/echo/client/echo_main.c b/channels/echo/client/echo_main.c
new file mode 100644
index 0000000..c4e1c22
--- /dev/null
+++ b/channels/echo/client/echo_main.c
@@ -0,0 +1,92 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Echo Virtual Channel Extension
+ *
+ * Copyright 2013 Christian Hofstaedtler
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/stream.h>
+
+#include "echo_main.h"
+#include <freerdp/client/channels.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/channels/echo.h>
+
+#define TAG CHANNELS_TAG("echo.client")
+
+typedef struct
+{
+ GENERIC_DYNVC_PLUGIN baseDynPlugin;
+} ECHO_PLUGIN;
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT echo_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+ const BYTE* pBuffer = Stream_ConstPointer(data);
+ const size_t cbSize = Stream_GetRemainingLength(data);
+
+ WINPR_ASSERT(callback);
+ WINPR_ASSERT(callback->channel);
+ WINPR_ASSERT(callback->channel->Write);
+
+ if (cbSize > UINT32_MAX)
+ return ERROR_INVALID_PARAMETER;
+
+ /* echo back what we have received. ECHO does not have any message IDs. */
+ return callback->channel->Write(callback->channel, (ULONG)cbSize, pBuffer, NULL);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT echo_on_close(IWTSVirtualChannelCallback* pChannelCallback)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+
+ free(callback);
+
+ return CHANNEL_RC_OK;
+}
+
+static const IWTSVirtualChannelCallback echo_callbacks = { echo_on_data_received, NULL, /* Open */
+ echo_on_close, NULL };
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT echo_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
+{
+ return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, ECHO_DVC_CHANNEL_NAME,
+ sizeof(ECHO_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK),
+ &echo_callbacks, NULL, NULL);
+}
diff --git a/channels/echo/client/echo_main.h b/channels/echo/client/echo_main.h
new file mode 100644
index 0000000..1286371
--- /dev/null
+++ b/channels/echo/client/echo_main.h
@@ -0,0 +1,40 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Echo Virtual Channel Extension
+ *
+ * Copyright 2013 Christian Hofstaedtler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_ECHO_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_ECHO_CLIENT_MAIN_H
+
+#include <freerdp/config.h>
+
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/log.h>
+
+#define DVC_TAG CHANNELS_TAG("echo.client")
+#ifdef WITH_DEBUG_DVC
+#define DEBUG_DVC(...) WLog_DBG(DVC_TAG, __VA_ARGS__)
+#else
+#define DEBUG_DVC(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#endif /* FREERDP_CHANNEL_ECHO_CLIENT_MAIN_H */
diff --git a/channels/echo/server/CMakeLists.txt b/channels/echo/server/CMakeLists.txt
new file mode 100644
index 0000000..2d13946
--- /dev/null
+++ b/channels/echo/server/CMakeLists.txt
@@ -0,0 +1,27 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_server("echo")
+
+set(${MODULE_PREFIX}_SRCS
+ echo_main.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry")
diff --git a/channels/echo/server/echo_main.c b/channels/echo/server/echo_main.c
new file mode 100644
index 0000000..9c16526
--- /dev/null
+++ b/channels/echo/server/echo_main.c
@@ -0,0 +1,381 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Echo Virtual Channel Extension
+ *
+ * Copyright 2014 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <winpr/assert.h>
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/sysinfo.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/server/echo.h>
+#include <freerdp/channels/echo.h>
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("echo.server")
+
+typedef struct
+{
+ echo_server_context context;
+
+ BOOL opened;
+
+ HANDLE stopEvent;
+
+ HANDLE thread;
+ void* echo_channel;
+
+ DWORD SessionId;
+
+} echo_server;
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT echo_server_open_channel(echo_server* echo)
+{
+ DWORD Error = 0;
+ HANDLE hEvent = NULL;
+ DWORD StartTick = 0;
+ DWORD BytesReturned = 0;
+ PULONG pSessionId = NULL;
+
+ if (WTSQuerySessionInformationA(echo->context.vcm, WTS_CURRENT_SESSION, WTSSessionId,
+ (LPSTR*)&pSessionId, &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSQuerySessionInformationA failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ echo->SessionId = (DWORD)*pSessionId;
+ WTSFreeMemory(pSessionId);
+ hEvent = WTSVirtualChannelManagerGetEventHandle(echo->context.vcm);
+ StartTick = GetTickCount();
+
+ while (echo->echo_channel == NULL)
+ {
+ if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED)
+ {
+ Error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error);
+ return Error;
+ }
+
+ echo->echo_channel = WTSVirtualChannelOpenEx(echo->SessionId, ECHO_DVC_CHANNEL_NAME,
+ WTS_CHANNEL_OPTION_DYNAMIC);
+
+ if (echo->echo_channel)
+ {
+ UINT32 channelId = 0;
+ BOOL status = TRUE;
+
+ channelId = WTSChannelGetIdByHandle(echo->echo_channel);
+
+ IFCALLRET(echo->context.ChannelIdAssigned, status, &echo->context, channelId);
+ if (!status)
+ {
+ WLog_ERR(TAG, "context->ChannelIdAssigned failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ break;
+ }
+
+ Error = GetLastError();
+
+ if (Error == ERROR_NOT_FOUND)
+ break;
+
+ if (GetTickCount() - StartTick > 5000)
+ break;
+ }
+
+ return echo->echo_channel ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR;
+}
+
+static DWORD WINAPI echo_server_thread_func(LPVOID arg)
+{
+ wStream* s = NULL;
+ void* buffer = NULL;
+ DWORD nCount = 0;
+ HANDLE events[8];
+ BOOL ready = FALSE;
+ HANDLE ChannelEvent = NULL;
+ DWORD BytesReturned = 0;
+ echo_server* echo = (echo_server*)arg;
+ UINT error = 0;
+ DWORD status = 0;
+
+ if ((error = echo_server_open_channel(echo)))
+ {
+ UINT error2 = 0;
+ WLog_ERR(TAG, "echo_server_open_channel failed with error %" PRIu32 "!", error);
+ IFCALLRET(echo->context.OpenResult, error2, &echo->context,
+ ECHO_SERVER_OPEN_RESULT_NOTSUPPORTED);
+
+ if (error2)
+ WLog_ERR(TAG, "echo server's OpenResult callback failed with error %" PRIu32 "",
+ error2);
+
+ goto out;
+ }
+
+ buffer = NULL;
+ BytesReturned = 0;
+ ChannelEvent = NULL;
+
+ if (WTSVirtualChannelQuery(echo->echo_channel, WTSVirtualEventHandle, &buffer,
+ &BytesReturned) == TRUE)
+ {
+ if (BytesReturned == sizeof(HANDLE))
+ CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE));
+
+ WTSFreeMemory(buffer);
+ }
+
+ nCount = 0;
+ events[nCount++] = echo->stopEvent;
+ events[nCount++] = ChannelEvent;
+
+ /* Wait for the client to confirm that the Graphics Pipeline dynamic channel is ready */
+
+ while (1)
+ {
+ status = WaitForMultipleObjects(nCount, events, FALSE, 100);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error);
+ break;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ {
+ IFCALLRET(echo->context.OpenResult, error, &echo->context,
+ ECHO_SERVER_OPEN_RESULT_CLOSED);
+
+ if (error)
+ WLog_ERR(TAG, "OpenResult failed with error %" PRIu32 "!", error);
+
+ break;
+ }
+
+ if (WTSVirtualChannelQuery(echo->echo_channel, WTSVirtualChannelReady, &buffer,
+ &BytesReturned) == FALSE)
+ {
+ IFCALLRET(echo->context.OpenResult, error, &echo->context,
+ ECHO_SERVER_OPEN_RESULT_ERROR);
+
+ if (error)
+ WLog_ERR(TAG, "OpenResult failed with error %" PRIu32 "!", error);
+
+ break;
+ }
+
+ ready = *((BOOL*)buffer);
+ WTSFreeMemory(buffer);
+
+ if (ready)
+ {
+ IFCALLRET(echo->context.OpenResult, error, &echo->context, ECHO_SERVER_OPEN_RESULT_OK);
+
+ if (error)
+ WLog_ERR(TAG, "OpenResult failed with error %" PRIu32 "!", error);
+
+ break;
+ }
+ }
+
+ s = Stream_New(NULL, 4096);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ WTSVirtualChannelClose(echo->echo_channel);
+ ExitThread(ERROR_NOT_ENOUGH_MEMORY);
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ while (ready)
+ {
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error);
+ break;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ Stream_SetPosition(s, 0);
+ WTSVirtualChannelRead(echo->echo_channel, 0, NULL, 0, &BytesReturned);
+
+ if (BytesReturned < 1)
+ continue;
+
+ if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ break;
+ }
+
+ if (WTSVirtualChannelRead(echo->echo_channel, 0, (PCHAR)Stream_Buffer(s),
+ (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ IFCALLRET(echo->context.Response, error, &echo->context, (BYTE*)Stream_Buffer(s),
+ BytesReturned);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error);
+ break;
+ }
+ }
+
+ Stream_Free(s, TRUE);
+ WTSVirtualChannelClose(echo->echo_channel);
+ echo->echo_channel = NULL;
+out:
+
+ if (error && echo->context.rdpcontext)
+ setChannelError(echo->context.rdpcontext, error,
+ "echo_server_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT echo_server_open(echo_server_context* context)
+{
+ echo_server* echo = (echo_server*)context;
+
+ if (echo->thread == NULL)
+ {
+ if (!(echo->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!(echo->thread = CreateThread(NULL, 0, echo_server_thread_func, (void*)echo, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ CloseHandle(echo->stopEvent);
+ echo->stopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT echo_server_close(echo_server_context* context)
+{
+ UINT error = CHANNEL_RC_OK;
+ echo_server* echo = (echo_server*)context;
+
+ if (echo->thread)
+ {
+ SetEvent(echo->stopEvent);
+
+ if (WaitForSingleObject(echo->thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ CloseHandle(echo->thread);
+ CloseHandle(echo->stopEvent);
+ echo->thread = NULL;
+ echo->stopEvent = NULL;
+ }
+
+ return error;
+}
+
+static BOOL echo_server_request(echo_server_context* context, const BYTE* buffer, UINT32 length)
+{
+ union
+ {
+ const BYTE* cpv;
+ CHAR* pv;
+ } cnv;
+ cnv.cpv = buffer;
+ echo_server* echo = (echo_server*)context;
+ WINPR_ASSERT(echo);
+
+ return WTSVirtualChannelWrite(echo->echo_channel, cnv.pv, length, NULL);
+}
+
+echo_server_context* echo_server_context_new(HANDLE vcm)
+{
+ echo_server* echo = NULL;
+ echo = (echo_server*)calloc(1, sizeof(echo_server));
+
+ if (echo)
+ {
+ echo->context.vcm = vcm;
+ echo->context.Open = echo_server_open;
+ echo->context.Close = echo_server_close;
+ echo->context.Request = echo_server_request;
+ }
+ else
+ WLog_ERR(TAG, "calloc failed!");
+
+ return (echo_server_context*)echo;
+}
+
+void echo_server_context_free(echo_server_context* context)
+{
+ echo_server* echo = (echo_server*)context;
+ echo_server_close(context);
+ free(echo);
+}
diff --git a/channels/encomsp/CMakeLists.txt b/channels/encomsp/CMakeLists.txt
new file mode 100644
index 0000000..be2d374
--- /dev/null
+++ b/channels/encomsp/CMakeLists.txt
@@ -0,0 +1,26 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("encomsp")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/encomsp/ChannelOptions.cmake b/channels/encomsp/ChannelOptions.cmake
new file mode 100644
index 0000000..82ef07e
--- /dev/null
+++ b/channels/encomsp/ChannelOptions.cmake
@@ -0,0 +1,13 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT ON)
+
+define_channel_options(NAME "encomsp" TYPE "static"
+ DESCRIPTION "Multiparty Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPEMC]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/encomsp/client/CMakeLists.txt b/channels/encomsp/client/CMakeLists.txt
new file mode 100644
index 0000000..59eaa8f
--- /dev/null
+++ b/channels/encomsp/client/CMakeLists.txt
@@ -0,0 +1,27 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("encomsp")
+
+include_directories(..)
+
+set(${MODULE_PREFIX}_SRCS
+ encomsp_main.c
+ encomsp_main.h
+)
+
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx")
diff --git a/channels/encomsp/client/encomsp_main.c b/channels/encomsp/client/encomsp_main.c
new file mode 100644
index 0000000..18b856e
--- /dev/null
+++ b/channels/encomsp/client/encomsp_main.c
@@ -0,0 +1,1304 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Multiparty Virtual Channel
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/print.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/client/encomsp.h>
+
+#include "encomsp_main.h"
+
+struct encomsp_plugin
+{
+ CHANNEL_DEF channelDef;
+ CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints;
+
+ EncomspClientContext* context;
+
+ HANDLE thread;
+ wStream* data_in;
+ void* InitHandle;
+ DWORD OpenHandle;
+ wMessageQueue* queue;
+ rdpContext* rdpcontext;
+};
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_read_header(wStream* s, ENCOMSP_ORDER_HEADER* header)
+{
+ WINPR_ASSERT(header);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, ENCOMSP_ORDER_HEADER_SIZE))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, header->Type); /* Type (2 bytes) */
+ Stream_Read_UINT16(s, header->Length); /* Length (2 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_write_header(wStream* s, const ENCOMSP_ORDER_HEADER* header)
+{
+ WINPR_ASSERT(header);
+ Stream_Write_UINT16(s, header->Type); /* Type (2 bytes) */
+ Stream_Write_UINT16(s, header->Length); /* Length (2 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_read_unicode_string(wStream* s, ENCOMSP_UNICODE_STRING* str)
+{
+ WINPR_ASSERT(str);
+ const ENCOMSP_UNICODE_STRING empty = { 0 };
+ *str = empty;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, str->cchString); /* cchString (2 bytes) */
+
+ if (str->cchString > 1024)
+ {
+ WLog_ERR(TAG, "cchString was %" PRIu16 " but has to be < 1025!", str->cchString);
+ return ERROR_INVALID_DATA;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, str->cchString, sizeof(WCHAR)))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read(s, &(str->wString), (str->cchString * 2)); /* String (variable) */
+ return CHANNEL_RC_OK;
+}
+
+static EncomspClientContext* encomsp_get_client_interface(encomspPlugin* encomsp)
+{
+ WINPR_ASSERT(encomsp);
+ return (EncomspClientContext*)encomsp->channelEntryPoints.pInterface;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_virtual_channel_write(encomspPlugin* encomsp, wStream* s)
+{
+ if (!encomsp)
+ {
+ Stream_Free(s, TRUE);
+ return ERROR_INVALID_HANDLE;
+ }
+
+#if 0
+ WLog_INFO(TAG, "EncomspWrite (%"PRIuz")", Stream_Length(s));
+ winpr_HexDump(Stream_Buffer(s), Stream_Length(s));
+#endif
+ const UINT status = encomsp->channelEntryPoints.pVirtualChannelWriteEx(
+ encomsp->InitHandle, encomsp->OpenHandle, Stream_Buffer(s), (UINT32)Stream_Length(s), s);
+
+ if (status != CHANNEL_RC_OK)
+ {
+ Stream_Free(s, TRUE);
+ WLog_ERR(TAG, "VirtualChannelWriteEx failed with %s [%08" PRIX32 "]",
+ WTSErrorToString(status), status);
+ }
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_recv_filter_updated_pdu(encomspPlugin* encomsp, wStream* s,
+ const ENCOMSP_ORDER_HEADER* header)
+{
+ ENCOMSP_FILTER_UPDATED_PDU pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+ EncomspClientContext* context = encomsp_get_client_interface(encomsp);
+
+ if (!context)
+ return ERROR_INVALID_HANDLE;
+
+ WINPR_ASSERT(header);
+ const size_t pos = Stream_GetPosition(s);
+ if (pos < ENCOMSP_ORDER_HEADER_SIZE)
+ return ERROR_INVALID_DATA;
+ const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE;
+ pdu.Length = header->Length;
+ pdu.Type = header->Type;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT8(s, pdu.Flags); /* Flags (1 byte) */
+ const size_t end = Stream_GetPosition(s);
+ const size_t body = beg + header->Length;
+
+ if (body < end)
+ {
+ WLog_ERR(TAG, "Not enough data!");
+ return ERROR_INVALID_DATA;
+ }
+
+ if (body > end)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end)))
+ return ERROR_INVALID_DATA;
+
+ Stream_SetPosition(s, body);
+ }
+
+ IFCALLRET(context->FilterUpdated, error, context, &pdu);
+
+ if (error)
+ WLog_ERR(TAG, "context->FilterUpdated failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_recv_application_created_pdu(encomspPlugin* encomsp, wStream* s,
+ const ENCOMSP_ORDER_HEADER* header)
+{
+ ENCOMSP_APPLICATION_CREATED_PDU pdu = { 0 };
+ EncomspClientContext* context = encomsp_get_client_interface(encomsp);
+
+ if (!context)
+ return ERROR_INVALID_HANDLE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return ERROR_INVALID_DATA;
+
+ const size_t pos = Stream_GetPosition(s);
+ if (pos < ENCOMSP_ORDER_HEADER_SIZE)
+ return ERROR_INVALID_DATA;
+ const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE;
+
+ WINPR_ASSERT(header);
+ pdu.Length = header->Length;
+ pdu.Type = header->Type;
+
+ Stream_Read_UINT16(s, pdu.Flags); /* Flags (2 bytes) */
+ Stream_Read_UINT32(s, pdu.AppId); /* AppId (4 bytes) */
+
+ UINT error = encomsp_read_unicode_string(s, &(pdu.Name));
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "encomsp_read_unicode_string failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ const size_t end = Stream_GetPosition(s);
+ const size_t body = beg + header->Length;
+
+ if (body < end)
+ {
+ WLog_ERR(TAG, "Not enough data!");
+ return ERROR_INVALID_DATA;
+ }
+
+ if (body > end)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end)))
+ return ERROR_INVALID_DATA;
+
+ Stream_SetPosition(s, body);
+ }
+
+ IFCALLRET(context->ApplicationCreated, error, context, &pdu);
+
+ if (error)
+ WLog_ERR(TAG, "context->ApplicationCreated failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_recv_application_removed_pdu(encomspPlugin* encomsp, wStream* s,
+ const ENCOMSP_ORDER_HEADER* header)
+{
+ ENCOMSP_APPLICATION_REMOVED_PDU pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+ EncomspClientContext* context = encomsp_get_client_interface(encomsp);
+
+ if (!context)
+ return ERROR_INVALID_HANDLE;
+
+ const size_t pos = Stream_GetPosition(s);
+ if (pos < ENCOMSP_ORDER_HEADER_SIZE)
+ return ERROR_INVALID_DATA;
+ const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE;
+
+ WINPR_ASSERT(header);
+ pdu.Length = header->Length;
+ pdu.Type = header->Type;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, pdu.AppId); /* AppId (4 bytes) */
+ const size_t end = Stream_GetPosition(s);
+ const size_t body = beg + header->Length;
+
+ if (body < end)
+ {
+ WLog_ERR(TAG, "Not enough data!");
+ return ERROR_INVALID_DATA;
+ }
+
+ if (body > end)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end)))
+ return ERROR_INVALID_DATA;
+
+ Stream_SetPosition(s, body);
+ }
+
+ IFCALLRET(context->ApplicationRemoved, error, context, &pdu);
+
+ if (error)
+ WLog_ERR(TAG, "context->ApplicationRemoved failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_recv_window_created_pdu(encomspPlugin* encomsp, wStream* s,
+ const ENCOMSP_ORDER_HEADER* header)
+{
+ ENCOMSP_WINDOW_CREATED_PDU pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+ EncomspClientContext* context = encomsp_get_client_interface(encomsp);
+
+ if (!context)
+ return ERROR_INVALID_HANDLE;
+
+ const size_t pos = Stream_GetPosition(s);
+ if (pos < ENCOMSP_ORDER_HEADER_SIZE)
+ return ERROR_INVALID_DATA;
+ const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE;
+
+ WINPR_ASSERT(header);
+ pdu.Length = header->Length;
+ pdu.Type = header->Type;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 10))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.Flags); /* Flags (2 bytes) */
+ Stream_Read_UINT32(s, pdu.AppId); /* AppId (4 bytes) */
+ Stream_Read_UINT32(s, pdu.WndId); /* WndId (4 bytes) */
+
+ if ((error = encomsp_read_unicode_string(s, &(pdu.Name))))
+ {
+ WLog_ERR(TAG, "encomsp_read_unicode_string failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ const size_t end = Stream_GetPosition(s);
+ const size_t body = beg + header->Length;
+
+ if (body < end)
+ {
+ WLog_ERR(TAG, "Not enough data!");
+ return ERROR_INVALID_DATA;
+ }
+
+ if (body > end)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end)))
+ return ERROR_INVALID_DATA;
+
+ Stream_SetPosition(s, body);
+ }
+
+ IFCALLRET(context->WindowCreated, error, context, &pdu);
+
+ if (error)
+ WLog_ERR(TAG, "context->WindowCreated failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_recv_window_removed_pdu(encomspPlugin* encomsp, wStream* s,
+ const ENCOMSP_ORDER_HEADER* header)
+{
+ ENCOMSP_WINDOW_REMOVED_PDU pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+ EncomspClientContext* context = encomsp_get_client_interface(encomsp);
+
+ if (!context)
+ return ERROR_INVALID_HANDLE;
+
+ const size_t pos = Stream_GetPosition(s);
+ if (pos < ENCOMSP_ORDER_HEADER_SIZE)
+ return ERROR_INVALID_DATA;
+ const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE;
+
+ WINPR_ASSERT(header);
+ pdu.Length = header->Length;
+ pdu.Type = header->Type;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, pdu.WndId); /* WndId (4 bytes) */
+ const size_t end = Stream_GetPosition(s);
+ const size_t body = beg + header->Length;
+
+ if (body < end)
+ {
+ WLog_ERR(TAG, "Not enough data!");
+ return ERROR_INVALID_DATA;
+ }
+
+ if (body > end)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end)))
+ return ERROR_INVALID_DATA;
+
+ Stream_SetPosition(s, body);
+ }
+
+ IFCALLRET(context->WindowRemoved, error, context, &pdu);
+
+ if (error)
+ WLog_ERR(TAG, "context->WindowRemoved failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_recv_show_window_pdu(encomspPlugin* encomsp, wStream* s,
+ const ENCOMSP_ORDER_HEADER* header)
+{
+ ENCOMSP_SHOW_WINDOW_PDU pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+ EncomspClientContext* context = encomsp_get_client_interface(encomsp);
+
+ if (!context)
+ return ERROR_INVALID_HANDLE;
+
+ const size_t pos = Stream_GetPosition(s);
+ if (pos < ENCOMSP_ORDER_HEADER_SIZE)
+ return ERROR_INVALID_DATA;
+ const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE;
+
+ WINPR_ASSERT(header);
+ pdu.Length = header->Length;
+ pdu.Type = header->Type;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, pdu.WndId); /* WndId (4 bytes) */
+ const size_t end = Stream_GetPosition(s);
+ const size_t body = beg + header->Length;
+
+ if (body < end)
+ {
+ WLog_ERR(TAG, "Not enough data!");
+ return ERROR_INVALID_DATA;
+ }
+
+ if (body > end)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end)))
+ return ERROR_INVALID_DATA;
+
+ Stream_SetPosition(s, body);
+ }
+
+ IFCALLRET(context->ShowWindow, error, context, &pdu);
+
+ if (error)
+ WLog_ERR(TAG, "context->ShowWindow failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_recv_participant_created_pdu(encomspPlugin* encomsp, wStream* s,
+ const ENCOMSP_ORDER_HEADER* header)
+{
+ ENCOMSP_PARTICIPANT_CREATED_PDU pdu = { 0 };
+ EncomspClientContext* context = encomsp_get_client_interface(encomsp);
+
+ if (!context)
+ return ERROR_INVALID_HANDLE;
+
+ const size_t pos = Stream_GetPosition(s);
+ if (pos < ENCOMSP_ORDER_HEADER_SIZE)
+ return ERROR_INVALID_DATA;
+ const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE;
+
+ WINPR_ASSERT(header);
+ pdu.Length = header->Length;
+ pdu.Type = header->Type;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 10))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, pdu.ParticipantId); /* ParticipantId (4 bytes) */
+ Stream_Read_UINT32(s, pdu.GroupId); /* GroupId (4 bytes) */
+ Stream_Read_UINT16(s, pdu.Flags); /* Flags (2 bytes) */
+
+ UINT error = encomsp_read_unicode_string(s, &(pdu.FriendlyName));
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "encomsp_read_unicode_string failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ const size_t end = Stream_GetPosition(s);
+ const size_t body = beg + header->Length;
+
+ if (body < end)
+ {
+ WLog_ERR(TAG, "Not enough data!");
+ return ERROR_INVALID_DATA;
+ }
+
+ if (body > end)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end)))
+ return ERROR_INVALID_DATA;
+
+ Stream_SetPosition(s, body);
+ }
+
+ IFCALLRET(context->ParticipantCreated, error, context, &pdu);
+
+ if (error)
+ WLog_ERR(TAG, "context->ParticipantCreated failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_recv_participant_removed_pdu(encomspPlugin* encomsp, wStream* s,
+ const ENCOMSP_ORDER_HEADER* header)
+{
+ ENCOMSP_PARTICIPANT_REMOVED_PDU pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+ EncomspClientContext* context = encomsp_get_client_interface(encomsp);
+
+ if (!context)
+ return ERROR_INVALID_HANDLE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return ERROR_INVALID_DATA;
+
+ const size_t beg = (Stream_GetPosition(s)) - ENCOMSP_ORDER_HEADER_SIZE;
+
+ WINPR_ASSERT(header);
+ pdu.Length = header->Length;
+ pdu.Type = header->Type;
+
+ Stream_Read_UINT32(s, pdu.ParticipantId); /* ParticipantId (4 bytes) */
+ Stream_Read_UINT32(s, pdu.DiscType); /* DiscType (4 bytes) */
+ Stream_Read_UINT32(s, pdu.DiscCode); /* DiscCode (4 bytes) */
+ const size_t end = Stream_GetPosition(s);
+ const size_t body = beg + header->Length;
+
+ if (body < end)
+ {
+ WLog_ERR(TAG, "Not enough data!");
+ return ERROR_INVALID_DATA;
+ }
+
+ if (body > end)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end)))
+ return ERROR_INVALID_DATA;
+
+ Stream_SetPosition(s, body);
+ }
+
+ IFCALLRET(context->ParticipantRemoved, error, context, &pdu);
+
+ if (error)
+ WLog_ERR(TAG, "context->ParticipantRemoved failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_recv_change_participant_control_level_pdu(encomspPlugin* encomsp, wStream* s,
+ const ENCOMSP_ORDER_HEADER* header)
+{
+ ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+ EncomspClientContext* context = encomsp_get_client_interface(encomsp);
+
+ if (!context)
+ return ERROR_INVALID_HANDLE;
+
+ const size_t pos = Stream_GetPosition(s);
+ if (pos < ENCOMSP_ORDER_HEADER_SIZE)
+ return ERROR_INVALID_DATA;
+ const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE;
+
+ WINPR_ASSERT(header);
+ pdu.Length = header->Length;
+ pdu.Type = header->Type;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.Flags); /* Flags (2 bytes) */
+ Stream_Read_UINT32(s, pdu.ParticipantId); /* ParticipantId (4 bytes) */
+ const size_t end = Stream_GetPosition(s);
+ const size_t body = beg + header->Length;
+
+ if (body < end)
+ {
+ WLog_ERR(TAG, "Not enough data!");
+ return ERROR_INVALID_DATA;
+ }
+
+ if (body > end)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end)))
+ return ERROR_INVALID_DATA;
+
+ Stream_SetPosition(s, body);
+ }
+
+ IFCALLRET(context->ChangeParticipantControlLevel, error, context, &pdu);
+
+ if (error)
+ WLog_ERR(TAG, "context->ChangeParticipantControlLevel failed with error %" PRIu32 "",
+ error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_send_change_participant_control_level_pdu(
+ EncomspClientContext* context, const ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU* pdu)
+{
+ ENCOMSP_ORDER_HEADER header = { 0 };
+
+ WINPR_ASSERT(context);
+ encomspPlugin* encomsp = (encomspPlugin*)context->handle;
+
+ header.Type = ODTYPE_PARTICIPANT_CTRL_CHANGED;
+ header.Length = ENCOMSP_ORDER_HEADER_SIZE + 6;
+
+ wStream* s = Stream_New(NULL, header.Length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ const UINT error = encomsp_write_header(s, &header);
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "encomsp_write_header failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ Stream_Write_UINT16(s, pdu->Flags); /* Flags (2 bytes) */
+ Stream_Write_UINT32(s, pdu->ParticipantId); /* ParticipantId (4 bytes) */
+ Stream_SealLength(s);
+ return encomsp_virtual_channel_write(encomsp, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_recv_graphics_stream_paused_pdu(encomspPlugin* encomsp, wStream* s,
+ const ENCOMSP_ORDER_HEADER* header)
+{
+ ENCOMSP_GRAPHICS_STREAM_PAUSED_PDU pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+ EncomspClientContext* context = encomsp_get_client_interface(encomsp);
+
+ if (!context)
+ return ERROR_INVALID_HANDLE;
+
+ const size_t pos = Stream_GetPosition(s);
+ if (pos < ENCOMSP_ORDER_HEADER_SIZE)
+ return ERROR_INVALID_DATA;
+ const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE;
+
+ WINPR_ASSERT(header);
+ pdu.Length = header->Length;
+ pdu.Type = header->Type;
+
+ const size_t end = Stream_GetPosition(s);
+ const size_t body = beg + header->Length;
+
+ if (body < end)
+ {
+ WLog_ERR(TAG, "Not enough data!");
+ return ERROR_INVALID_DATA;
+ }
+
+ if (body > end)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end)))
+ return ERROR_INVALID_DATA;
+
+ Stream_SetPosition(s, body);
+ }
+
+ IFCALLRET(context->GraphicsStreamPaused, error, context, &pdu);
+
+ if (error)
+ WLog_ERR(TAG, "context->GraphicsStreamPaused failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_recv_graphics_stream_resumed_pdu(encomspPlugin* encomsp, wStream* s,
+ const ENCOMSP_ORDER_HEADER* header)
+{
+ ENCOMSP_GRAPHICS_STREAM_RESUMED_PDU pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+ EncomspClientContext* context = encomsp_get_client_interface(encomsp);
+
+ if (!context)
+ return ERROR_INVALID_HANDLE;
+
+ const size_t pos = Stream_GetPosition(s);
+ if (pos < ENCOMSP_ORDER_HEADER_SIZE)
+ return ERROR_INVALID_DATA;
+ const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE;
+
+ WINPR_ASSERT(header);
+ pdu.Length = header->Length;
+ pdu.Type = header->Type;
+
+ const size_t end = Stream_GetPosition(s);
+ const size_t body = beg + header->Length;
+
+ if (body < end)
+ {
+ WLog_ERR(TAG, "Not enough data!");
+ return ERROR_INVALID_DATA;
+ }
+
+ if (body > end)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end)))
+ return ERROR_INVALID_DATA;
+
+ Stream_SetPosition(s, body);
+ }
+
+ IFCALLRET(context->GraphicsStreamResumed, error, context, &pdu);
+
+ if (error)
+ WLog_ERR(TAG, "context->GraphicsStreamResumed failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_process_receive(encomspPlugin* encomsp, wStream* s)
+{
+ UINT error = CHANNEL_RC_OK;
+ ENCOMSP_ORDER_HEADER header = { 0 };
+
+ WINPR_ASSERT(encomsp);
+ while (Stream_GetRemainingLength(s) > 0)
+ {
+ if ((error = encomsp_read_header(s, &header)))
+ {
+ WLog_ERR(TAG, "encomsp_read_header failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ // WLog_DBG(TAG, "EncomspReceive: Type: %"PRIu16" Length: %"PRIu16"", header.Type,
+ // header.Length);
+
+ switch (header.Type)
+ {
+ case ODTYPE_FILTER_STATE_UPDATED:
+ if ((error = encomsp_recv_filter_updated_pdu(encomsp, s, &header)))
+ {
+ WLog_ERR(TAG, "encomsp_recv_filter_updated_pdu failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ case ODTYPE_APP_REMOVED:
+ if ((error = encomsp_recv_application_removed_pdu(encomsp, s, &header)))
+ {
+ WLog_ERR(TAG,
+ "encomsp_recv_application_removed_pdu failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ case ODTYPE_APP_CREATED:
+ if ((error = encomsp_recv_application_created_pdu(encomsp, s, &header)))
+ {
+ WLog_ERR(TAG,
+ "encomsp_recv_application_removed_pdu failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ case ODTYPE_WND_REMOVED:
+ if ((error = encomsp_recv_window_removed_pdu(encomsp, s, &header)))
+ {
+ WLog_ERR(TAG, "encomsp_recv_window_removed_pdu failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ case ODTYPE_WND_CREATED:
+ if ((error = encomsp_recv_window_created_pdu(encomsp, s, &header)))
+ {
+ WLog_ERR(TAG, "encomsp_recv_window_created_pdu failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ case ODTYPE_WND_SHOW:
+ if ((error = encomsp_recv_show_window_pdu(encomsp, s, &header)))
+ {
+ WLog_ERR(TAG, "encomsp_recv_show_window_pdu failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ case ODTYPE_PARTICIPANT_REMOVED:
+ if ((error = encomsp_recv_participant_removed_pdu(encomsp, s, &header)))
+ {
+ WLog_ERR(TAG,
+ "encomsp_recv_participant_removed_pdu failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ case ODTYPE_PARTICIPANT_CREATED:
+ if ((error = encomsp_recv_participant_created_pdu(encomsp, s, &header)))
+ {
+ WLog_ERR(TAG,
+ "encomsp_recv_participant_created_pdu failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ case ODTYPE_PARTICIPANT_CTRL_CHANGED:
+ if ((error =
+ encomsp_recv_change_participant_control_level_pdu(encomsp, s, &header)))
+ {
+ WLog_ERR(TAG,
+ "encomsp_recv_change_participant_control_level_pdu failed with error "
+ "%" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ case ODTYPE_GRAPHICS_STREAM_PAUSED:
+ if ((error = encomsp_recv_graphics_stream_paused_pdu(encomsp, s, &header)))
+ {
+ WLog_ERR(TAG,
+ "encomsp_recv_graphics_stream_paused_pdu failed with error %" PRIu32
+ "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ case ODTYPE_GRAPHICS_STREAM_RESUMED:
+ if ((error = encomsp_recv_graphics_stream_resumed_pdu(encomsp, s, &header)))
+ {
+ WLog_ERR(TAG,
+ "encomsp_recv_graphics_stream_resumed_pdu failed with error %" PRIu32
+ "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "header.Type %" PRIu16 " not found", header.Type);
+ return ERROR_INVALID_DATA;
+ }
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_virtual_channel_event_data_received(encomspPlugin* encomsp, const void* pData,
+ UINT32 dataLength, UINT32 totalLength,
+ UINT32 dataFlags)
+{
+ WINPR_ASSERT(encomsp);
+
+ if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME))
+ return CHANNEL_RC_OK;
+
+ if (dataFlags & CHANNEL_FLAG_FIRST)
+ {
+ if (encomsp->data_in)
+ Stream_Free(encomsp->data_in, TRUE);
+
+ encomsp->data_in = Stream_New(NULL, totalLength);
+
+ if (!encomsp->data_in)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+
+ wStream* data_in = encomsp->data_in;
+
+ if (!Stream_EnsureRemainingCapacity(data_in, dataLength))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Write(data_in, pData, dataLength);
+
+ if (dataFlags & CHANNEL_FLAG_LAST)
+ {
+ if (Stream_Capacity(data_in) != Stream_GetPosition(data_in))
+ {
+ WLog_ERR(TAG, "encomsp_plugin_process_received: read error");
+ return ERROR_INVALID_DATA;
+ }
+
+ encomsp->data_in = NULL;
+ Stream_SealLength(data_in);
+ Stream_SetPosition(data_in, 0);
+
+ if (!MessageQueue_Post(encomsp->queue, NULL, 0, (void*)data_in, NULL))
+ {
+ WLog_ERR(TAG, "MessageQueue_Post failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static VOID VCAPITYPE encomsp_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle,
+ UINT event, LPVOID pData,
+ UINT32 dataLength, UINT32 totalLength,
+ UINT32 dataFlags)
+{
+ UINT error = CHANNEL_RC_OK;
+ encomspPlugin* encomsp = (encomspPlugin*)lpUserParam;
+
+ switch (event)
+ {
+ case CHANNEL_EVENT_DATA_RECEIVED:
+ if (!encomsp || (encomsp->OpenHandle != openHandle))
+ {
+ WLog_ERR(TAG, "error no match");
+ return;
+ }
+ if ((error = encomsp_virtual_channel_event_data_received(encomsp, pData, dataLength,
+ totalLength, dataFlags)))
+ WLog_ERR(TAG,
+ "encomsp_virtual_channel_event_data_received failed with error %" PRIu32
+ "",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_WRITE_CANCELLED:
+ case CHANNEL_EVENT_WRITE_COMPLETE:
+ {
+ wStream* s = (wStream*)pData;
+ Stream_Free(s, TRUE);
+ }
+ break;
+
+ case CHANNEL_EVENT_USER:
+ break;
+ }
+
+ if (error && encomsp && encomsp->rdpcontext)
+ setChannelError(encomsp->rdpcontext, error,
+ "encomsp_virtual_channel_open_event reported an error");
+
+ return;
+}
+
+static DWORD WINAPI encomsp_virtual_channel_client_thread(LPVOID arg)
+{
+ wStream* data = NULL;
+ wMessage message = { 0 };
+ encomspPlugin* encomsp = (encomspPlugin*)arg;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(encomsp);
+ while (1)
+ {
+ if (!MessageQueue_Wait(encomsp->queue))
+ {
+ WLog_ERR(TAG, "MessageQueue_Wait failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (!MessageQueue_Peek(encomsp->queue, &message, TRUE))
+ {
+ WLog_ERR(TAG, "MessageQueue_Peek failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (message.id == WMQ_QUIT)
+ break;
+
+ if (message.id == 0)
+ {
+ data = (wStream*)message.wParam;
+
+ if ((error = encomsp_process_receive(encomsp, data)))
+ {
+ WLog_ERR(TAG, "encomsp_process_receive failed with error %" PRIu32 "!", error);
+ Stream_Free(data, TRUE);
+ break;
+ }
+
+ Stream_Free(data, TRUE);
+ }
+ }
+
+ if (error && encomsp->rdpcontext)
+ setChannelError(encomsp->rdpcontext, error,
+ "encomsp_virtual_channel_client_thread reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_virtual_channel_event_connected(encomspPlugin* encomsp, LPVOID pData,
+ UINT32 dataLength)
+{
+ WINPR_ASSERT(encomsp);
+
+ encomsp->queue = MessageQueue_New(NULL);
+
+ if (!encomsp->queue)
+ {
+ WLog_ERR(TAG, "MessageQueue_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if (!(encomsp->thread = CreateThread(NULL, 0, encomsp_virtual_channel_client_thread,
+ (void*)encomsp, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ MessageQueue_Free(encomsp->queue);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return encomsp->channelEntryPoints.pVirtualChannelOpenEx(
+ encomsp->InitHandle, &encomsp->OpenHandle, encomsp->channelDef.name,
+ encomsp_virtual_channel_open_event_ex);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_virtual_channel_event_disconnected(encomspPlugin* encomsp)
+{
+ WINPR_ASSERT(encomsp);
+ if (encomsp->OpenHandle == 0)
+ return CHANNEL_RC_OK;
+
+ if (encomsp->queue && encomsp->thread)
+ {
+ if (MessageQueue_PostQuit(encomsp->queue, 0) &&
+ (WaitForSingleObject(encomsp->thread, INFINITE) == WAIT_FAILED))
+ {
+ const UINT rc = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", rc);
+ return rc;
+ }
+ }
+
+ MessageQueue_Free(encomsp->queue);
+ CloseHandle(encomsp->thread);
+ encomsp->queue = NULL;
+ encomsp->thread = NULL;
+
+ WINPR_ASSERT(encomsp->channelEntryPoints.pVirtualChannelCloseEx);
+ const UINT rc = encomsp->channelEntryPoints.pVirtualChannelCloseEx(encomsp->InitHandle,
+ encomsp->OpenHandle);
+
+ if (CHANNEL_RC_OK != rc)
+ {
+ WLog_ERR(TAG, "pVirtualChannelClose failed with %s [%08" PRIX32 "]", WTSErrorToString(rc),
+ rc);
+ return rc;
+ }
+
+ encomsp->OpenHandle = 0;
+
+ if (encomsp->data_in)
+ {
+ Stream_Free(encomsp->data_in, TRUE);
+ encomsp->data_in = NULL;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_virtual_channel_event_terminated(encomspPlugin* encomsp)
+{
+ WINPR_ASSERT(encomsp);
+
+ encomsp->InitHandle = 0;
+ free(encomsp->context);
+ free(encomsp);
+ return CHANNEL_RC_OK;
+}
+
+static VOID VCAPITYPE encomsp_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle,
+ UINT event, LPVOID pData,
+ UINT dataLength)
+{
+ UINT error = CHANNEL_RC_OK;
+ encomspPlugin* encomsp = (encomspPlugin*)lpUserParam;
+
+ if (!encomsp || (encomsp->InitHandle != pInitHandle))
+ {
+ WLog_ERR(TAG, "error no match");
+ return;
+ }
+
+ switch (event)
+ {
+ case CHANNEL_EVENT_INITIALIZED:
+ break;
+
+ case CHANNEL_EVENT_CONNECTED:
+ if ((error = encomsp_virtual_channel_event_connected(encomsp, pData, dataLength)))
+ WLog_ERR(TAG,
+ "encomsp_virtual_channel_event_connected failed with error %" PRIu32 "",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_DISCONNECTED:
+ if ((error = encomsp_virtual_channel_event_disconnected(encomsp)))
+ WLog_ERR(TAG,
+ "encomsp_virtual_channel_event_disconnected failed with error %" PRIu32 "",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_TERMINATED:
+ encomsp_virtual_channel_event_terminated(encomsp);
+ break;
+
+ default:
+ break;
+ }
+
+ if (error && encomsp->rdpcontext)
+ setChannelError(encomsp->rdpcontext, error,
+ "encomsp_virtual_channel_init_event reported an error");
+}
+
+/* encomsp is always built-in */
+#define VirtualChannelEntryEx encomsp_VirtualChannelEntryEx
+
+FREERDP_ENTRY_POINT(BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS_EX pEntryPoints,
+ PVOID pInitHandle))
+{
+ BOOL isFreerdp = FALSE;
+ encomspPlugin* encomsp = (encomspPlugin*)calloc(1, sizeof(encomspPlugin));
+
+ if (!encomsp)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return FALSE;
+ }
+
+ encomsp->channelDef.options = CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP |
+ CHANNEL_OPTION_COMPRESS_RDP | CHANNEL_OPTION_SHOW_PROTOCOL;
+ sprintf_s(encomsp->channelDef.name, ARRAYSIZE(encomsp->channelDef.name),
+ ENCOMSP_SVC_CHANNEL_NAME);
+ CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx =
+ (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints;
+ WINPR_ASSERT(pEntryPointsEx);
+
+ EncomspClientContext* context = NULL;
+ if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) &&
+ (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER))
+ {
+ context = (EncomspClientContext*)calloc(1, sizeof(EncomspClientContext));
+
+ if (!context)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ goto error_out;
+ }
+
+ context->handle = (void*)encomsp;
+ context->FilterUpdated = NULL;
+ context->ApplicationCreated = NULL;
+ context->ApplicationRemoved = NULL;
+ context->WindowCreated = NULL;
+ context->WindowRemoved = NULL;
+ context->ShowWindow = NULL;
+ context->ParticipantCreated = NULL;
+ context->ParticipantRemoved = NULL;
+ context->ChangeParticipantControlLevel = encomsp_send_change_participant_control_level_pdu;
+ context->GraphicsStreamPaused = NULL;
+ context->GraphicsStreamResumed = NULL;
+ encomsp->context = context;
+ encomsp->rdpcontext = pEntryPointsEx->context;
+ isFreerdp = TRUE;
+ }
+
+ CopyMemory(&(encomsp->channelEntryPoints), pEntryPoints,
+ sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX));
+ encomsp->InitHandle = pInitHandle;
+ const UINT rc = encomsp->channelEntryPoints.pVirtualChannelInitEx(
+ encomsp, context, pInitHandle, &encomsp->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000,
+ encomsp_virtual_channel_init_event_ex);
+
+ if (CHANNEL_RC_OK != rc)
+ {
+ WLog_ERR(TAG, "failed with %s [%08" PRIX32 "]", WTSErrorToString(rc), rc);
+ goto error_out;
+ }
+
+ encomsp->channelEntryPoints.pInterface = context;
+ return TRUE;
+error_out:
+
+ if (isFreerdp)
+ free(encomsp->context);
+
+ free(encomsp);
+ return FALSE;
+}
diff --git a/channels/encomsp/client/encomsp_main.h b/channels/encomsp/client/encomsp_main.h
new file mode 100644
index 0000000..ad43cea
--- /dev/null
+++ b/channels/encomsp/client/encomsp_main.h
@@ -0,0 +1,42 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Multiparty Virtual Channel
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_ENCOMSP_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_ENCOMSP_CLIENT_MAIN_H
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+
+#include <freerdp/api.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/svc.h>
+#include <freerdp/addin.h>
+
+#include <freerdp/client/encomsp.h>
+
+#define TAG CHANNELS_TAG("encomsp.client")
+
+typedef struct encomsp_plugin encomspPlugin;
+
+#endif /* FREERDP_CHANNEL_ENCOMSP_CLIENT_MAIN_H */
diff --git a/channels/encomsp/server/CMakeLists.txt b/channels/encomsp/server/CMakeLists.txt
new file mode 100644
index 0000000..096e6d0
--- /dev/null
+++ b/channels/encomsp/server/CMakeLists.txt
@@ -0,0 +1,30 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_server("encomsp")
+
+include_directories(..)
+
+set(${MODULE_PREFIX}_SRCS
+ encomsp_main.c
+ encomsp_main.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+)
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry")
diff --git a/channels/encomsp/server/encomsp_main.c b/channels/encomsp/server/encomsp_main.c
new file mode 100644
index 0000000..f6390df
--- /dev/null
+++ b/channels/encomsp/server/encomsp_main.c
@@ -0,0 +1,372 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Multiparty Virtual Channel
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/log.h>
+
+#include "encomsp_main.h"
+
+#define TAG CHANNELS_TAG("encomsp.server")
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_read_header(wStream* s, ENCOMSP_ORDER_HEADER* header)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, ENCOMSP_ORDER_HEADER_SIZE))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, header->Type); /* Type (2 bytes) */
+ Stream_Read_UINT16(s, header->Length); /* Length (2 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+#if 0
+
+static int encomsp_write_header(wStream* s, ENCOMSP_ORDER_HEADER* header)
+{
+ Stream_Write_UINT16(s, header->Type); /* Type (2 bytes) */
+ Stream_Write_UINT16(s, header->Length); /* Length (2 bytes) */
+ return 1;
+}
+
+static int encomsp_read_unicode_string(wStream* s, ENCOMSP_UNICODE_STRING* str)
+{
+ ZeroMemory(str, sizeof(ENCOMSP_UNICODE_STRING));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return -1;
+
+ Stream_Read_UINT16(s, str->cchString); /* cchString (2 bytes) */
+
+ if (str->cchString > 1024)
+ return -1;
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, str->cchString, sizeof(WCHAR)))
+ return -1;
+
+ Stream_Read(s, &(str->wString), (str->cchString * 2)); /* String (variable) */
+ return 1;
+}
+
+#endif
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_recv_change_participant_control_level_pdu(EncomspServerContext* context,
+ wStream* s,
+ ENCOMSP_ORDER_HEADER* header)
+{
+ int beg = 0;
+ int end = 0;
+ ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU pdu;
+ UINT error = CHANNEL_RC_OK;
+ beg = ((int)Stream_GetPosition(s)) - ENCOMSP_ORDER_HEADER_SIZE;
+ CopyMemory(&pdu, header, sizeof(ENCOMSP_ORDER_HEADER));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.Flags); /* Flags (2 bytes) */
+ Stream_Read_UINT32(s, pdu.ParticipantId); /* ParticipantId (4 bytes) */
+ end = (int)Stream_GetPosition(s);
+
+ if ((beg + header->Length) < end)
+ {
+ WLog_ERR(TAG, "Not enough data!");
+ return ERROR_INVALID_DATA;
+ }
+
+ if ((beg + header->Length) > end)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)((beg + header->Length) - end)))
+ return ERROR_INVALID_DATA;
+
+ Stream_SetPosition(s, (beg + header->Length));
+ }
+
+ IFCALLRET(context->ChangeParticipantControlLevel, error, context, &pdu);
+
+ if (error)
+ WLog_ERR(TAG, "context->ChangeParticipantControlLevel failed with error %" PRIu32 "",
+ error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_server_receive_pdu(EncomspServerContext* context, wStream* s)
+{
+ UINT error = CHANNEL_RC_OK;
+ ENCOMSP_ORDER_HEADER header;
+
+ while (Stream_GetRemainingLength(s) > 0)
+ {
+ if ((error = encomsp_read_header(s, &header)))
+ {
+ WLog_ERR(TAG, "encomsp_read_header failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ WLog_INFO(TAG, "EncomspReceive: Type: %" PRIu16 " Length: %" PRIu16 "", header.Type,
+ header.Length);
+
+ switch (header.Type)
+ {
+ case ODTYPE_PARTICIPANT_CTRL_CHANGED:
+ if ((error =
+ encomsp_recv_change_participant_control_level_pdu(context, s, &header)))
+ {
+ WLog_ERR(TAG,
+ "encomsp_recv_change_participant_control_level_pdu failed with error "
+ "%" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "header.Type unknown %" PRIu16 "!", header.Type);
+ return ERROR_INVALID_DATA;
+ }
+ }
+
+ return error;
+}
+
+static DWORD WINAPI encomsp_server_thread(LPVOID arg)
+{
+ wStream* s = NULL;
+ DWORD nCount = 0;
+ void* buffer = NULL;
+ HANDLE events[8];
+ HANDLE ChannelEvent = NULL;
+ DWORD BytesReturned = 0;
+ ENCOMSP_ORDER_HEADER* header = NULL;
+ EncomspServerContext* context = NULL;
+ UINT error = CHANNEL_RC_OK;
+ DWORD status = 0;
+ context = (EncomspServerContext*)arg;
+
+ buffer = NULL;
+ BytesReturned = 0;
+ ChannelEvent = NULL;
+ s = Stream_New(NULL, 4096);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ if (WTSVirtualChannelQuery(context->priv->ChannelHandle, WTSVirtualEventHandle, &buffer,
+ &BytesReturned) == TRUE)
+ {
+ if (BytesReturned == sizeof(HANDLE))
+ CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE));
+
+ WTSFreeMemory(buffer);
+ }
+
+ nCount = 0;
+ events[nCount++] = ChannelEvent;
+ events[nCount++] = context->priv->StopEvent;
+
+ while (1)
+ {
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error);
+ break;
+ }
+
+ status = WaitForSingleObject(context->priv->StopEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ break;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ {
+ break;
+ }
+
+ WTSVirtualChannelRead(context->priv->ChannelHandle, 0, NULL, 0, &BytesReturned);
+
+ if (BytesReturned < 1)
+ continue;
+
+ if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ break;
+ }
+
+ if (!WTSVirtualChannelRead(context->priv->ChannelHandle, 0, (PCHAR)Stream_Buffer(s),
+ Stream_Capacity(s), &BytesReturned))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (Stream_GetPosition(s) >= ENCOMSP_ORDER_HEADER_SIZE)
+ {
+ header = (ENCOMSP_ORDER_HEADER*)Stream_Buffer(s);
+
+ if (header->Length >= Stream_GetPosition(s))
+ {
+ Stream_SealLength(s);
+ Stream_SetPosition(s, 0);
+
+ if ((error = encomsp_server_receive_pdu(context, s)))
+ {
+ WLog_ERR(TAG, "encomsp_server_receive_pdu failed with error %" PRIu32 "!",
+ error);
+ break;
+ }
+
+ Stream_SetPosition(s, 0);
+ }
+ }
+ }
+
+ Stream_Free(s, TRUE);
+out:
+
+ if (error && context->rdpcontext)
+ setChannelError(context->rdpcontext, error, "encomsp_server_thread reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_server_start(EncomspServerContext* context)
+{
+ context->priv->ChannelHandle =
+ WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, ENCOMSP_SVC_CHANNEL_NAME);
+
+ if (!context->priv->ChannelHandle)
+ return CHANNEL_RC_BAD_CHANNEL;
+
+ if (!(context->priv->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!(context->priv->Thread =
+ CreateThread(NULL, 0, encomsp_server_thread, (void*)context, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ CloseHandle(context->priv->StopEvent);
+ context->priv->StopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT encomsp_server_stop(EncomspServerContext* context)
+{
+ UINT error = CHANNEL_RC_OK;
+ SetEvent(context->priv->StopEvent);
+
+ if (WaitForSingleObject(context->priv->Thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ CloseHandle(context->priv->Thread);
+ CloseHandle(context->priv->StopEvent);
+ return error;
+}
+
+EncomspServerContext* encomsp_server_context_new(HANDLE vcm)
+{
+ EncomspServerContext* context = NULL;
+ context = (EncomspServerContext*)calloc(1, sizeof(EncomspServerContext));
+
+ if (context)
+ {
+ context->vcm = vcm;
+ context->Start = encomsp_server_start;
+ context->Stop = encomsp_server_stop;
+ context->priv = (EncomspServerPrivate*)calloc(1, sizeof(EncomspServerPrivate));
+
+ if (!context->priv)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ free(context);
+ return NULL;
+ }
+ }
+
+ return context;
+}
+
+void encomsp_server_context_free(EncomspServerContext* context)
+{
+ if (context)
+ {
+ if (context->priv->ChannelHandle != INVALID_HANDLE_VALUE)
+ WTSVirtualChannelClose(context->priv->ChannelHandle);
+
+ free(context->priv);
+ free(context);
+ }
+}
diff --git a/channels/encomsp/server/encomsp_main.h b/channels/encomsp/server/encomsp_main.h
new file mode 100644
index 0000000..37d8c71
--- /dev/null
+++ b/channels/encomsp/server/encomsp_main.h
@@ -0,0 +1,36 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Multiparty Virtual Channel
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_ENCOMSP_SERVER_MAIN_H
+#define FREERDP_CHANNEL_ENCOMSP_SERVER_MAIN_H
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+#include <freerdp/server/encomsp.h>
+
+struct s_encomsp_server_private
+{
+ HANDLE Thread;
+ HANDLE StopEvent;
+ void* ChannelHandle;
+};
+
+#endif /* FREERDP_CHANNEL_ENCOMSP_SERVER_MAIN_H */
diff --git a/channels/geometry/CMakeLists.txt b/channels/geometry/CMakeLists.txt
new file mode 100644
index 0000000..7ddea6d
--- /dev/null
+++ b/channels/geometry/CMakeLists.txt
@@ -0,0 +1,22 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2017 David Fort <contact@hardening-consulting.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("geometry")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/geometry/ChannelOptions.cmake b/channels/geometry/ChannelOptions.cmake
new file mode 100644
index 0000000..8e8163b
--- /dev/null
+++ b/channels/geometry/ChannelOptions.cmake
@@ -0,0 +1,11 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT OFF)
+
+define_channel_options(NAME "geometry" TYPE "dynamic"
+ DESCRIPTION "Geometry tracking Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPEGT]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
diff --git a/channels/geometry/client/CMakeLists.txt b/channels/geometry/client/CMakeLists.txt
new file mode 100644
index 0000000..b069bb1
--- /dev/null
+++ b/channels/geometry/client/CMakeLists.txt
@@ -0,0 +1,31 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2017 David Fort <contact@hardening-consulting.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("geometry")
+
+set(${MODULE_PREFIX}_SRCS
+ geometry_main.c
+ geometry_main.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+)
+
+include_directories(..)
+
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
diff --git a/channels/geometry/client/geometry_main.c b/channels/geometry/client/geometry_main.c
new file mode 100644
index 0000000..5465fc9
--- /dev/null
+++ b/channels/geometry/client/geometry_main.c
@@ -0,0 +1,402 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Geometry tracking Virtual Channel Extension
+ *
+ * Copyright 2017 David Fort <contact@hardening-consulting.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+#include <winpr/cmdline.h>
+#include <winpr/collections.h>
+
+#include <freerdp/addin.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/client/geometry.h>
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("geometry.client")
+
+#include "geometry_main.h"
+
+typedef struct
+{
+ GENERIC_DYNVC_PLUGIN base;
+ GeometryClientContext* context;
+} GEOMETRY_PLUGIN;
+
+static UINT32 mappedGeometryHash(const void* v)
+{
+ const UINT64* g = (const UINT64*)v;
+ return (UINT32)((*g >> 32) + (*g & 0xffffffff));
+}
+
+static BOOL mappedGeometryKeyCompare(const void* v1, const void* v2)
+{
+ const UINT64* g1 = (const UINT64*)v1;
+ const UINT64* g2 = (const UINT64*)v2;
+ return *g1 == *g2;
+}
+
+static void freerdp_rgndata_reset(FREERDP_RGNDATA* data)
+{
+ data->nRectCount = 0;
+}
+
+static UINT32 geometry_read_RGNDATA(wLog* logger, wStream* s, UINT32 len, FREERDP_RGNDATA* rgndata)
+{
+ UINT32 dwSize = 0;
+ UINT32 iType = 0;
+ INT32 right = 0;
+ INT32 bottom = 0;
+ INT32 x = 0;
+ INT32 y = 0;
+ INT32 w = 0;
+ INT32 h = 0;
+
+ if (len < 32)
+ {
+ WLog_Print(logger, WLOG_ERROR, "invalid RGNDATA");
+ return ERROR_INVALID_DATA;
+ }
+
+ Stream_Read_UINT32(s, dwSize);
+
+ if (dwSize != 32)
+ {
+ WLog_Print(logger, WLOG_ERROR, "invalid RGNDATA dwSize");
+ return ERROR_INVALID_DATA;
+ }
+
+ Stream_Read_UINT32(s, iType);
+
+ if (iType != RDH_RECTANGLE)
+ {
+ WLog_Print(logger, WLOG_ERROR, "iType %" PRIu32 " for RGNDATA is not supported", iType);
+ return ERROR_UNSUPPORTED_TYPE;
+ }
+
+ Stream_Read_UINT32(s, rgndata->nRectCount);
+ Stream_Seek_UINT32(s); /* nRgnSize IGNORED */
+ Stream_Read_INT32(s, x);
+ Stream_Read_INT32(s, y);
+ Stream_Read_INT32(s, right);
+ Stream_Read_INT32(s, bottom);
+ if ((abs(x) > INT16_MAX) || (abs(y) > INT16_MAX))
+ return ERROR_INVALID_DATA;
+ w = right - x;
+ h = bottom - y;
+ if ((abs(w) > INT16_MAX) || (abs(h) > INT16_MAX))
+ return ERROR_INVALID_DATA;
+ rgndata->boundingRect.x = (INT16)x;
+ rgndata->boundingRect.y = (INT16)y;
+ rgndata->boundingRect.width = (INT16)w;
+ rgndata->boundingRect.height = (INT16)h;
+ len -= 32;
+
+ if (len / (4 * 4) < rgndata->nRectCount)
+ {
+ WLog_Print(logger, WLOG_ERROR, "not enough data for region rectangles");
+ return ERROR_INVALID_DATA;
+ }
+
+ if (rgndata->nRectCount)
+ {
+ RDP_RECT* tmp = realloc(rgndata->rects, rgndata->nRectCount * sizeof(RDP_RECT));
+
+ if (!tmp)
+ {
+ WLog_Print(logger, WLOG_ERROR, "unable to allocate memory for %" PRIu32 " RECTs",
+ rgndata->nRectCount);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ rgndata->rects = tmp;
+
+ for (UINT32 i = 0; i < rgndata->nRectCount; i++)
+ {
+ Stream_Read_INT32(s, x);
+ Stream_Read_INT32(s, y);
+ Stream_Read_INT32(s, right);
+ Stream_Read_INT32(s, bottom);
+ if ((abs(x) > INT16_MAX) || (abs(y) > INT16_MAX))
+ return ERROR_INVALID_DATA;
+ w = right - x;
+ h = bottom - y;
+ if ((abs(w) > INT16_MAX) || (abs(h) > INT16_MAX))
+ return ERROR_INVALID_DATA;
+ rgndata->rects[i].x = (INT16)x;
+ rgndata->rects[i].y = (INT16)y;
+ rgndata->rects[i].width = (INT16)w;
+ rgndata->rects[i].height = (INT16)h;
+ }
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT geometry_recv_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ UINT32 length = 0;
+ UINT32 cbGeometryBuffer = 0;
+ MAPPED_GEOMETRY* mappedGeometry = NULL;
+ GEOMETRY_PLUGIN* geometry = NULL;
+ GeometryClientContext* context = NULL;
+ UINT ret = CHANNEL_RC_OK;
+ UINT32 updateType = 0;
+ UINT32 geometryType = 0;
+ UINT64 id = 0;
+ wLog* logger = NULL;
+
+ geometry = (GEOMETRY_PLUGIN*)callback->plugin;
+ logger = geometry->base.log;
+ context = (GeometryClientContext*)geometry->base.iface.pInterface;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, length); /* Length (4 bytes) */
+
+ if (length < 73 || !Stream_CheckAndLogRequiredLength(TAG, s, (length - 4)))
+ {
+ WLog_Print(logger, WLOG_ERROR, "invalid packet length");
+ return ERROR_INVALID_DATA;
+ }
+
+ Stream_Read_UINT32(s, context->remoteVersion);
+ Stream_Read_UINT64(s, id);
+ Stream_Read_UINT32(s, updateType);
+ Stream_Seek_UINT32(s); /* flags */
+
+ mappedGeometry = HashTable_GetItemValue(context->geometries, &id);
+
+ if (updateType == GEOMETRY_CLEAR)
+ {
+ if (!mappedGeometry)
+ {
+ WLog_Print(logger, WLOG_ERROR,
+ "geometry 0x%" PRIx64 " not found here, ignoring clear command", id);
+ return CHANNEL_RC_OK;
+ }
+
+ WLog_Print(logger, WLOG_DEBUG, "clearing geometry 0x%" PRIx64 "", id);
+
+ if (mappedGeometry->MappedGeometryClear &&
+ !mappedGeometry->MappedGeometryClear(mappedGeometry))
+ return ERROR_INTERNAL_ERROR;
+
+ if (!HashTable_Remove(context->geometries, &id))
+ WLog_Print(logger, WLOG_ERROR, "geometry not removed from geometries");
+ }
+ else if (updateType == GEOMETRY_UPDATE)
+ {
+ BOOL newOne = FALSE;
+
+ if (!mappedGeometry)
+ {
+ newOne = TRUE;
+ WLog_Print(logger, WLOG_DEBUG, "creating geometry 0x%" PRIx64 "", id);
+ mappedGeometry = calloc(1, sizeof(MAPPED_GEOMETRY));
+ if (!mappedGeometry)
+ return CHANNEL_RC_NO_MEMORY;
+
+ mappedGeometry->refCounter = 1;
+ mappedGeometry->mappingId = id;
+
+ if (!HashTable_Insert(context->geometries, &(mappedGeometry->mappingId),
+ mappedGeometry))
+ {
+ WLog_Print(logger, WLOG_ERROR,
+ "unable to register geometry 0x%" PRIx64 " in the table", id);
+ free(mappedGeometry);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+ else
+ {
+ WLog_Print(logger, WLOG_DEBUG, "updating geometry 0x%" PRIx64 "", id);
+ }
+
+ Stream_Read_UINT64(s, mappedGeometry->topLevelId);
+
+ Stream_Read_INT32(s, mappedGeometry->left);
+ Stream_Read_INT32(s, mappedGeometry->top);
+ Stream_Read_INT32(s, mappedGeometry->right);
+ Stream_Read_INT32(s, mappedGeometry->bottom);
+
+ Stream_Read_INT32(s, mappedGeometry->topLevelLeft);
+ Stream_Read_INT32(s, mappedGeometry->topLevelTop);
+ Stream_Read_INT32(s, mappedGeometry->topLevelRight);
+ Stream_Read_INT32(s, mappedGeometry->topLevelBottom);
+
+ Stream_Read_UINT32(s, geometryType);
+ if (geometryType != 0x02)
+ WLog_Print(logger, WLOG_DEBUG, "geometryType should be set to 0x02 and is 0x%" PRIx32,
+ geometryType);
+
+ Stream_Read_UINT32(s, cbGeometryBuffer);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, cbGeometryBuffer))
+ {
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert ownership mappedGeometry
+ return ERROR_INVALID_DATA;
+ }
+
+ if (cbGeometryBuffer)
+ {
+ ret = geometry_read_RGNDATA(logger, s, cbGeometryBuffer, &mappedGeometry->geometry);
+ if (ret != CHANNEL_RC_OK)
+ return ret;
+ }
+ else
+ {
+ freerdp_rgndata_reset(&mappedGeometry->geometry);
+ }
+
+ if (newOne)
+ {
+ if (context->MappedGeometryAdded &&
+ !context->MappedGeometryAdded(context, mappedGeometry))
+ {
+ WLog_Print(logger, WLOG_ERROR, "geometry added callback failed");
+ ret = ERROR_INTERNAL_ERROR;
+ }
+ }
+ else
+ {
+ if (mappedGeometry->MappedGeometryUpdate &&
+ !mappedGeometry->MappedGeometryUpdate(mappedGeometry))
+ {
+ WLog_Print(logger, WLOG_ERROR, "geometry update callback failed");
+ ret = ERROR_INTERNAL_ERROR;
+ }
+ }
+ }
+ else
+ {
+ WLog_Print(logger, WLOG_ERROR, "unknown updateType=%" PRIu32 "", updateType);
+ ret = CHANNEL_RC_OK;
+ }
+
+ return ret;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT geometry_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+ return geometry_recv_pdu(callback, data);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT geometry_on_close(IWTSVirtualChannelCallback* pChannelCallback)
+{
+ free(pChannelCallback);
+ return CHANNEL_RC_OK;
+}
+
+static void mappedGeometryUnref_void(void* arg)
+{
+ MAPPED_GEOMETRY* g = (MAPPED_GEOMETRY*)arg;
+ mappedGeometryUnref(g);
+}
+
+/**
+ * Channel Client Interface
+ */
+
+static const IWTSVirtualChannelCallback geometry_callbacks = { geometry_on_data_received,
+ NULL, /* Open */
+ geometry_on_close, NULL };
+
+static UINT init_plugin_cb(GENERIC_DYNVC_PLUGIN* base, rdpContext* rcontext, rdpSettings* settings)
+{
+ GeometryClientContext* context = NULL;
+ GEOMETRY_PLUGIN* geometry = (GEOMETRY_PLUGIN*)base;
+
+ WINPR_ASSERT(base);
+ WINPR_UNUSED(settings);
+
+ context = (GeometryClientContext*)calloc(1, sizeof(GeometryClientContext));
+ if (!context)
+ {
+ WLog_Print(base->log, WLOG_ERROR, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ context->geometries = HashTable_New(FALSE);
+ if (!context->geometries)
+ {
+ WLog_Print(base->log, WLOG_ERROR, "unable to allocate geometries");
+ free(context);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ HashTable_SetHashFunction(context->geometries, mappedGeometryHash);
+ {
+ wObject* obj = HashTable_KeyObject(context->geometries);
+ obj->fnObjectEquals = mappedGeometryKeyCompare;
+ }
+ {
+ wObject* obj = HashTable_ValueObject(context->geometries);
+ obj->fnObjectFree = mappedGeometryUnref_void;
+ }
+ context->handle = (void*)geometry;
+
+ geometry->context = context;
+ geometry->base.iface.pInterface = (void*)context;
+
+ return CHANNEL_RC_OK;
+}
+
+static void terminate_plugin_cb(GENERIC_DYNVC_PLUGIN* base)
+{
+ GEOMETRY_PLUGIN* geometry = (GEOMETRY_PLUGIN*)base;
+
+ if (geometry->context)
+ HashTable_Free(geometry->context->geometries);
+ free(geometry->context);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT geometry_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
+{
+ return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, GEOMETRY_DVC_CHANNEL_NAME,
+ sizeof(GEOMETRY_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK),
+ &geometry_callbacks, init_plugin_cb, terminate_plugin_cb);
+}
diff --git a/channels/geometry/client/geometry_main.h b/channels/geometry/client/geometry_main.h
new file mode 100644
index 0000000..dc914b6
--- /dev/null
+++ b/channels/geometry/client/geometry_main.h
@@ -0,0 +1,30 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Geometry tracking virtual channel extension
+ *
+ * Copyright 2017 David Fort <contact@hardening-consulting.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_GEOMETRY_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_GEOMETRY_CLIENT_MAIN_H
+
+#include <freerdp/config.h>
+
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+#include <freerdp/addin.h>
+#include <freerdp/client/geometry.h>
+
+#endif /* FREERDP_CHANNEL_GEOMETRY_CLIENT_MAIN_H */
diff --git a/channels/gfxredir/CMakeLists.txt b/channels/gfxredir/CMakeLists.txt
new file mode 100644
index 0000000..bfea757
--- /dev/null
+++ b/channels/gfxredir/CMakeLists.txt
@@ -0,0 +1,23 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2020 Microsoft
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+if(WITH_CHANNEL_GFXREDIR)
+ define_channel("gfxredir")
+ if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+ endif()
+endif()
diff --git a/channels/gfxredir/ChannelOptions.cmake b/channels/gfxredir/ChannelOptions.cmake
new file mode 100644
index 0000000..62cdceb
--- /dev/null
+++ b/channels/gfxredir/ChannelOptions.cmake
@@ -0,0 +1,12 @@
+if(WITH_CHANNEL_GFXREDIR)
+ set(OPTION_DEFAULT OFF)
+ set(OPTION_CLIENT_DEFAULT OFF)
+ set(OPTION_SERVER_DEFAULT ON)
+
+ define_channel_options(NAME "gfxredir" TYPE "dynamic"
+ DESCRIPTION "Graphics Redirection Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPXXXX]"
+ DEFAULT ${OPTION_DEFAULT})
+
+ define_channel_server_options(${OPTION_SERVER_DEFAULT})
+endif()
diff --git a/channels/gfxredir/gfxredir_common.c b/channels/gfxredir/gfxredir_common.c
new file mode 100644
index 0000000..552d7d2
--- /dev/null
+++ b/channels/gfxredir/gfxredir_common.c
@@ -0,0 +1,55 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDPXXXX Remote App Graphics Redirection Virtual Channel Extension
+ *
+ * Copyright 2020 Microsoft
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("gfxredir.common")
+
+#include "gfxredir_common.h"
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT gfxredir_read_header(wStream* s, GFXREDIR_HEADER* header)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, header->cmdId);
+ Stream_Read_UINT32(s, header->length);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT gfxredir_write_header(wStream* s, const GFXREDIR_HEADER* header)
+{
+ Stream_Write_UINT32(s, header->cmdId);
+ Stream_Write_UINT32(s, header->length);
+ return CHANNEL_RC_OK;
+}
diff --git a/channels/gfxredir/gfxredir_common.h b/channels/gfxredir/gfxredir_common.h
new file mode 100644
index 0000000..18710a9
--- /dev/null
+++ b/channels/gfxredir/gfxredir_common.h
@@ -0,0 +1,32 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDPXXXX Remote App Graphics Redirection Virtual Channel Extension
+ *
+ * Copyright 2020 Microsoft
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_GFXREDIR_COMMON_H
+#define FREERDP_CHANNEL_GFXREDIR_COMMON_H
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+
+#include <freerdp/channels/gfxredir.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL UINT gfxredir_read_header(wStream* s, GFXREDIR_HEADER* header);
+FREERDP_LOCAL UINT gfxredir_write_header(wStream* s, const GFXREDIR_HEADER* header);
+
+#endif /* FREERDP_CHANNEL_GFXREDIR_COMMON_H */
diff --git a/channels/gfxredir/server/CMakeLists.txt b/channels/gfxredir/server/CMakeLists.txt
new file mode 100644
index 0000000..19a57af
--- /dev/null
+++ b/channels/gfxredir/server/CMakeLists.txt
@@ -0,0 +1,32 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2020 Microsoft
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_server("gfxredir")
+
+set(${MODULE_PREFIX}_SRCS
+ gfxredir_main.c
+ gfxredir_main.h
+ ../gfxredir_common.c
+ ../gfxredir_common.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+include_directories(..)
+
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry")
diff --git a/channels/gfxredir/server/gfxredir_main.c b/channels/gfxredir/server/gfxredir_main.c
new file mode 100644
index 0000000..f661a9c
--- /dev/null
+++ b/channels/gfxredir/server/gfxredir_main.c
@@ -0,0 +1,794 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDPXXXX Remote App Graphics Redirection Virtual Channel Extension
+ *
+ * Copyright 2020 Microsoft
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include "gfxredir_main.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/sysinfo.h>
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/log.h>
+
+#include <freerdp/server/gfxredir.h>
+#include "../gfxredir_common.h"
+
+#define TAG CHANNELS_TAG("gfxredir.server")
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gfxredir_recv_legacy_caps_pdu(wStream* s, GfxRedirServerContext* context)
+{
+ UINT32 error = CHANNEL_RC_OK;
+ GFXREDIR_LEGACY_CAPS_PDU pdu;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.version); /* version (2 bytes) */
+
+ if (context)
+ IFCALLRET(context->GraphicsRedirectionLegacyCaps, error, context, &pdu);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gfxredir_recv_caps_advertise_pdu(wStream* s, UINT32 length,
+ GfxRedirServerContext* context)
+{
+ UINT32 error = CHANNEL_RC_OK;
+ GFXREDIR_CAPS_ADVERTISE_PDU pdu;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
+ return ERROR_INVALID_DATA;
+
+ pdu.length = length;
+ Stream_GetPointer(s, pdu.caps);
+ Stream_Seek(s, length);
+
+ if (!context->GraphicsRedirectionCapsAdvertise)
+ {
+ WLog_ERR(TAG, "server does not support CapsAdvertise PDU!");
+ return ERROR_NOT_SUPPORTED;
+ }
+ else if (context)
+ {
+ IFCALLRET(context->GraphicsRedirectionCapsAdvertise, error, context, &pdu);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gfxredir_recv_present_buffer_ack_pdu(wStream* s, GfxRedirServerContext* context)
+{
+ UINT32 error = CHANNEL_RC_OK;
+ GFXREDIR_PRESENT_BUFFER_ACK_PDU pdu;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 16))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT64(s, pdu.windowId); /* windowId (8 bytes) */
+ Stream_Read_UINT64(s, pdu.presentId); /* presentId (8 bytes) */
+
+ if (context)
+ {
+ IFCALLRET(context->PresentBufferAck, error, context, &pdu);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gfxredir_server_receive_pdu(GfxRedirServerContext* context, wStream* s)
+{
+ UINT error = CHANNEL_RC_OK;
+ size_t beg, end;
+ GFXREDIR_HEADER header;
+ beg = Stream_GetPosition(s);
+
+ if ((error = gfxredir_read_header(s, &header)))
+ {
+ WLog_ERR(TAG, "gfxredir_read_header failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ switch (header.cmdId)
+ {
+ case GFXREDIR_CMDID_LEGACY_CAPS:
+ if ((error = gfxredir_recv_legacy_caps_pdu(s, context)))
+ WLog_ERR(TAG,
+ "gfxredir_recv_legacy_caps_pdu "
+ "failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case GFXREDIR_CMDID_CAPS_ADVERTISE:
+ if ((error = gfxredir_recv_caps_advertise_pdu(s, header.length - 8, context)))
+ WLog_ERR(TAG,
+ "gfxredir_recv_caps_advertise "
+ "failed with error %" PRIu32 "!",
+ error);
+ break;
+
+ case GFXREDIR_CMDID_PRESENT_BUFFER_ACK:
+ if ((error = gfxredir_recv_present_buffer_ack_pdu(s, context)))
+ WLog_ERR(TAG,
+ "gfxredir_recv_present_buffer_ask_pdu "
+ "failed with error %" PRIu32 "!",
+ error);
+ break;
+
+ default:
+ error = CHANNEL_RC_BAD_PROC;
+ WLog_WARN(TAG, "Received unknown PDU type: %" PRIu32 "", header.cmdId);
+ break;
+ }
+
+ end = Stream_GetPosition(s);
+
+ if (end != (beg + header.length))
+ {
+ WLog_ERR(TAG, "Unexpected GFXREDIR pdu end: Actual: %d, Expected: %" PRIu32 "", end,
+ (beg + header.length));
+ Stream_SetPosition(s, (beg + header.length));
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gfxredir_server_handle_messages(GfxRedirServerContext* context)
+{
+ DWORD BytesReturned;
+ void* buffer;
+ UINT ret = CHANNEL_RC_OK;
+ GfxRedirServerPrivate* priv = context->priv;
+ wStream* s = priv->input_stream;
+
+ /* Check whether the dynamic channel is ready */
+ if (!priv->isReady)
+ {
+ if (WTSVirtualChannelQuery(priv->gfxredir_channel, WTSVirtualChannelReady, &buffer,
+ &BytesReturned) == FALSE)
+ {
+ if (GetLastError() == ERROR_NO_DATA)
+ return ERROR_NO_DATA;
+
+ WLog_ERR(TAG, "WTSVirtualChannelQuery failed");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ priv->isReady = *((BOOL*)buffer);
+ WTSFreeMemory(buffer);
+ }
+
+ /* Consume channel event only after the dynamic channel is ready */
+ if (priv->isReady)
+ {
+ Stream_SetPosition(s, 0);
+
+ if (!WTSVirtualChannelRead(priv->gfxredir_channel, 0, NULL, 0, &BytesReturned))
+ {
+ if (GetLastError() == ERROR_NO_DATA)
+ return ERROR_NO_DATA;
+
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (BytesReturned < 1)
+ return CHANNEL_RC_OK;
+
+ if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if (WTSVirtualChannelRead(priv->gfxredir_channel, 0, (PCHAR)Stream_Buffer(s),
+ Stream_Capacity(s), &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_SetLength(s, BytesReturned);
+ Stream_SetPosition(s, 0);
+
+ while (Stream_GetPosition(s) < Stream_Length(s))
+ {
+ if ((ret = gfxredir_server_receive_pdu(context, s)))
+ {
+ WLog_ERR(TAG,
+ "gfxredir_server_receive_pdu "
+ "failed with error %" PRIu32 "!",
+ ret);
+ return ret;
+ }
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static DWORD WINAPI gfxredir_server_thread_func(LPVOID arg)
+{
+ GfxRedirServerContext* context = (GfxRedirServerContext*)arg;
+ GfxRedirServerPrivate* priv = context->priv;
+ DWORD status;
+ DWORD nCount;
+ HANDLE events[8];
+ UINT error = CHANNEL_RC_OK;
+ nCount = 0;
+ events[nCount++] = priv->stopEvent;
+ events[nCount++] = priv->channelEvent;
+
+ while (TRUE)
+ {
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error);
+ break;
+ }
+
+ /* Stop Event */
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ if ((error = gfxredir_server_handle_messages(context)))
+ {
+ WLog_ERR(TAG, "gfxredir_server_handle_messages failed with error %" PRIu32 "", error);
+ break;
+ }
+ }
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ * Create new stream for single gfxredir packet. The new stream length
+ * would be required data length + header. The header will be written
+ * to the stream before return.
+ *
+ * @param cmdId
+ * @param length - data length without header
+ *
+ * @return new stream
+ */
+static wStream* gfxredir_server_single_packet_new(UINT32 cmdId, UINT32 length)
+{
+ UINT error;
+ GFXREDIR_HEADER header;
+ wStream* s = Stream_New(NULL, GFXREDIR_HEADER_SIZE + length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ goto error;
+ }
+
+ header.cmdId = cmdId;
+ header.length = GFXREDIR_HEADER_SIZE + length;
+
+ if ((error = gfxredir_write_header(s, &header)))
+ {
+ WLog_ERR(TAG, "Failed to write header with error %" PRIu32 "!", error);
+ goto error;
+ }
+
+ return s;
+error:
+ Stream_Free(s, TRUE);
+ return NULL;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gfxredir_server_packet_send(GfxRedirServerContext* context, wStream* s)
+{
+ UINT ret;
+ ULONG written;
+
+ if (!WTSVirtualChannelWrite(context->priv->gfxredir_channel, (PCHAR)Stream_Buffer(s),
+ Stream_GetPosition(s), &written))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
+ ret = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ if (written < Stream_GetPosition(s))
+ {
+ WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written,
+ Stream_GetPosition(s));
+ }
+
+ ret = CHANNEL_RC_OK;
+out:
+ Stream_Free(s, TRUE);
+ return ret;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gfxredir_send_error(GfxRedirServerContext* context, const GFXREDIR_ERROR_PDU* error)
+{
+ wStream* s = gfxredir_server_single_packet_new(GFXREDIR_CMDID_ERROR, 4);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "gfxredir_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT32(s, error->errorCode);
+ return gfxredir_server_packet_send(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gfxredir_send_caps_confirm(GfxRedirServerContext* context,
+ const GFXREDIR_CAPS_CONFIRM_PDU* graphicsCapsConfirm)
+{
+ UINT ret;
+ wStream* s;
+
+ if (graphicsCapsConfirm->length < GFXREDIR_CAPS_HEADER_SIZE)
+ {
+ WLog_ERR(TAG, "length must be greater than header size, failed!");
+ return ERROR_INVALID_DATA;
+ }
+
+ s = gfxredir_server_single_packet_new(GFXREDIR_CMDID_CAPS_CONFIRM, graphicsCapsConfirm->length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "gfxredir_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT32(s, GFXREDIR_CAPS_SIGNATURE);
+ Stream_Write_UINT32(s, graphicsCapsConfirm->version);
+ Stream_Write_UINT32(s, graphicsCapsConfirm->length);
+ if (graphicsCapsConfirm->length > GFXREDIR_CAPS_HEADER_SIZE)
+ Stream_Write(s, graphicsCapsConfirm->capsData,
+ graphicsCapsConfirm->length - GFXREDIR_CAPS_HEADER_SIZE);
+ ret = gfxredir_server_packet_send(context, s);
+ if (ret == CHANNEL_RC_OK)
+ context->confirmedCapsVersion = graphicsCapsConfirm->version;
+ return ret;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gfxredir_send_open_pool(GfxRedirServerContext* context,
+ const GFXREDIR_OPEN_POOL_PDU* openPool)
+{
+ wStream* s;
+
+ if (context->confirmedCapsVersion != GFXREDIR_CAPS_VERSION2_0)
+ {
+ WLog_ERR(TAG, "open_pool is called with invalid version!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (openPool->sectionNameLength == 0 || openPool->sectionName == NULL)
+ {
+ WLog_ERR(TAG, "section name must be provided!");
+ return ERROR_INVALID_DATA;
+ }
+
+ /* make sure null-terminate */
+ if (openPool->sectionName[openPool->sectionNameLength - 1] != 0)
+ {
+ WLog_ERR(TAG, "section name must be terminated with NULL!");
+ return ERROR_INVALID_DATA;
+ }
+
+ s = gfxredir_server_single_packet_new(GFXREDIR_CMDID_OPEN_POOL,
+ 20 + (openPool->sectionNameLength * 2));
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "gfxredir_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT64(s, openPool->poolId);
+ Stream_Write_UINT64(s, openPool->poolSize);
+ Stream_Write_UINT32(s, openPool->sectionNameLength);
+ Stream_Write(s, openPool->sectionName, (openPool->sectionNameLength * 2));
+ return gfxredir_server_packet_send(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gfxredir_send_close_pool(GfxRedirServerContext* context,
+ const GFXREDIR_CLOSE_POOL_PDU* closePool)
+{
+ wStream* s;
+
+ if (context->confirmedCapsVersion != GFXREDIR_CAPS_VERSION2_0)
+ {
+ WLog_ERR(TAG, "close_pool is called with invalid version!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ s = gfxredir_server_single_packet_new(GFXREDIR_CMDID_CLOSE_POOL, 8);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "gfxredir_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT64(s, closePool->poolId);
+ return gfxredir_server_packet_send(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gfxredir_send_create_buffer(GfxRedirServerContext* context,
+ const GFXREDIR_CREATE_BUFFER_PDU* createBuffer)
+{
+ wStream* s;
+
+ if (context->confirmedCapsVersion != GFXREDIR_CAPS_VERSION2_0)
+ {
+ WLog_ERR(TAG, "create_buffer is called with invalid version!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ s = gfxredir_server_single_packet_new(GFXREDIR_CMDID_CREATE_BUFFER, 40);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "gfxredir_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT64(s, createBuffer->poolId);
+ Stream_Write_UINT64(s, createBuffer->bufferId);
+ Stream_Write_UINT64(s, createBuffer->offset);
+ Stream_Write_UINT32(s, createBuffer->stride);
+ Stream_Write_UINT32(s, createBuffer->width);
+ Stream_Write_UINT32(s, createBuffer->height);
+ Stream_Write_UINT32(s, createBuffer->format);
+ return gfxredir_server_packet_send(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gfxredir_send_destroy_buffer(GfxRedirServerContext* context,
+ const GFXREDIR_DESTROY_BUFFER_PDU* destroyBuffer)
+{
+ wStream* s;
+
+ if (context->confirmedCapsVersion != GFXREDIR_CAPS_VERSION2_0)
+ {
+ WLog_ERR(TAG, "destroy_buffer is called with invalid version!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ s = gfxredir_server_single_packet_new(GFXREDIR_CMDID_DESTROY_BUFFER, 8);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "gfxredir_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT64(s, destroyBuffer->bufferId);
+ return gfxredir_server_packet_send(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gfxredir_send_present_buffer(GfxRedirServerContext* context,
+ const GFXREDIR_PRESENT_BUFFER_PDU* presentBuffer)
+{
+ UINT32 length;
+ wStream* s;
+ RECTANGLE_32 dummyRect = { 0, 0, 0, 0 };
+
+ if (context->confirmedCapsVersion != GFXREDIR_CAPS_VERSION2_0)
+ {
+ WLog_ERR(TAG, "present_buffer is called with invalid version!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (presentBuffer->numOpaqueRects > GFXREDIR_MAX_OPAQUE_RECTS)
+ {
+ WLog_ERR(TAG, "numOpaqueRects is larger than its limit!");
+ return ERROR_INVALID_DATA;
+ }
+
+ length = 64 + ((presentBuffer->numOpaqueRects ? presentBuffer->numOpaqueRects : 1) *
+ sizeof(RECTANGLE_32));
+
+ s = gfxredir_server_single_packet_new(GFXREDIR_CMDID_PRESENT_BUFFER, length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "gfxredir_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT64(s, presentBuffer->timestamp);
+ Stream_Write_UINT64(s, presentBuffer->presentId);
+ Stream_Write_UINT64(s, presentBuffer->windowId);
+ Stream_Write_UINT64(s, presentBuffer->bufferId);
+ Stream_Write_UINT32(s, presentBuffer->orientation);
+ Stream_Write_UINT32(s, presentBuffer->targetWidth);
+ Stream_Write_UINT32(s, presentBuffer->targetHeight);
+ Stream_Write_UINT32(s, presentBuffer->dirtyRect.left);
+ Stream_Write_UINT32(s, presentBuffer->dirtyRect.top);
+ Stream_Write_UINT32(s, presentBuffer->dirtyRect.width);
+ Stream_Write_UINT32(s, presentBuffer->dirtyRect.height);
+ Stream_Write_UINT32(s, presentBuffer->numOpaqueRects);
+ if (presentBuffer->numOpaqueRects)
+ Stream_Write(s, presentBuffer->opaqueRects,
+ (presentBuffer->numOpaqueRects * sizeof(RECTANGLE_32)));
+ else
+ Stream_Write(s, &dummyRect, sizeof(RECTANGLE_32));
+ return gfxredir_server_packet_send(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gfxredir_server_open(GfxRedirServerContext* context)
+{
+ UINT rc = ERROR_INTERNAL_ERROR;
+ GfxRedirServerPrivate* priv = context->priv;
+ DWORD BytesReturned = 0;
+ PULONG pSessionId = NULL;
+ void* buffer;
+ buffer = NULL;
+ priv->SessionId = WTS_CURRENT_SESSION;
+
+ if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId,
+ (LPSTR*)&pSessionId, &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSQuerySessionInformationA failed!");
+ rc = ERROR_INTERNAL_ERROR;
+ goto out_close;
+ }
+
+ priv->SessionId = (DWORD)*pSessionId;
+ WTSFreeMemory(pSessionId);
+ priv->gfxredir_channel = (HANDLE)WTSVirtualChannelOpenEx(
+ priv->SessionId, GFXREDIR_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC);
+
+ if (!priv->gfxredir_channel)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed!");
+ rc = GetLastError();
+ goto out_close;
+ }
+
+ /* Query for channel event handle */
+ if (!WTSVirtualChannelQuery(priv->gfxredir_channel, WTSVirtualEventHandle, &buffer,
+ &BytesReturned) ||
+ (BytesReturned != sizeof(HANDLE)))
+ {
+ WLog_ERR(TAG,
+ "WTSVirtualChannelQuery failed "
+ "or invalid returned size(%" PRIu32 ")",
+ BytesReturned);
+
+ if (buffer)
+ WTSFreeMemory(buffer);
+
+ rc = ERROR_INTERNAL_ERROR;
+ goto out_close;
+ }
+
+ CopyMemory(&priv->channelEvent, buffer, sizeof(HANDLE));
+ WTSFreeMemory(buffer);
+
+ if (priv->thread == NULL)
+ {
+ if (!(priv->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ rc = ERROR_INTERNAL_ERROR;
+ }
+
+ if (!(priv->thread =
+ CreateThread(NULL, 0, gfxredir_server_thread_func, (void*)context, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ CloseHandle(priv->stopEvent);
+ priv->stopEvent = NULL;
+ rc = ERROR_INTERNAL_ERROR;
+ }
+ }
+
+ return CHANNEL_RC_OK;
+out_close:
+ WTSVirtualChannelClose(priv->gfxredir_channel);
+ priv->gfxredir_channel = NULL;
+ priv->channelEvent = NULL;
+ return rc;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gfxredir_server_close(GfxRedirServerContext* context)
+{
+ UINT error = CHANNEL_RC_OK;
+ GfxRedirServerPrivate* priv = context->priv;
+
+ if (priv->thread)
+ {
+ SetEvent(priv->stopEvent);
+
+ if (WaitForSingleObject(priv->thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ CloseHandle(priv->thread);
+ CloseHandle(priv->stopEvent);
+ priv->thread = NULL;
+ priv->stopEvent = NULL;
+ }
+
+ if (priv->gfxredir_channel)
+ {
+ WTSVirtualChannelClose(priv->gfxredir_channel);
+ priv->gfxredir_channel = NULL;
+ }
+
+ return error;
+}
+
+GfxRedirServerContext* gfxredir_server_context_new(HANDLE vcm)
+{
+ GfxRedirServerContext* context;
+ GfxRedirServerPrivate* priv;
+ context = (GfxRedirServerContext*)calloc(1, sizeof(GfxRedirServerContext));
+
+ if (!context)
+ {
+ WLog_ERR(TAG, "gfxredir_server_context_new(): calloc GfxRedirServerContext failed!");
+ return NULL;
+ }
+
+ priv = context->priv = (GfxRedirServerPrivate*)calloc(1, sizeof(GfxRedirServerPrivate));
+
+ if (!context->priv)
+ {
+ WLog_ERR(TAG, "gfxredir_server_context_new(): calloc GfxRedirServerPrivate failed!");
+ goto out_free;
+ }
+
+ priv->input_stream = Stream_New(NULL, 4);
+
+ if (!priv->input_stream)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ goto out_free_priv;
+ }
+
+ context->vcm = vcm;
+ context->Open = gfxredir_server_open;
+ context->Close = gfxredir_server_close;
+ context->Error = gfxredir_send_error;
+ context->GraphicsRedirectionCapsConfirm = gfxredir_send_caps_confirm;
+ context->OpenPool = gfxredir_send_open_pool;
+ context->ClosePool = gfxredir_send_close_pool;
+ context->CreateBuffer = gfxredir_send_create_buffer;
+ context->DestroyBuffer = gfxredir_send_destroy_buffer;
+ context->PresentBuffer = gfxredir_send_present_buffer;
+ context->confirmedCapsVersion = GFXREDIR_CAPS_VERSION1;
+ priv->isReady = FALSE;
+ return context;
+out_free_priv:
+ free(context->priv);
+out_free:
+ free(context);
+ return NULL;
+}
+
+void gfxredir_server_context_free(GfxRedirServerContext* context)
+{
+ if (!context)
+ return;
+
+ gfxredir_server_close(context);
+
+ if (context->priv)
+ {
+ Stream_Free(context->priv->input_stream, TRUE);
+ free(context->priv);
+ }
+
+ free(context);
+}
diff --git a/channels/gfxredir/server/gfxredir_main.h b/channels/gfxredir/server/gfxredir_main.h
new file mode 100644
index 0000000..45424fd
--- /dev/null
+++ b/channels/gfxredir/server/gfxredir_main.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDPXXXX Remote App Graphics Redirection Virtual Channel Extension
+ *
+ * Copyright 2020 Microsoft
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_GFXREDIR_SERVER_MAIN_H
+#define FREERDP_CHANNEL_GFXREDIR_SERVER_MAIN_H
+
+#include <freerdp/server/gfxredir.h>
+
+struct s_gfxredir_server_private
+{
+ BOOL isReady;
+ wStream* input_stream;
+ HANDLE channelEvent;
+ HANDLE thread;
+ HANDLE stopEvent;
+ DWORD SessionId;
+
+ void* gfxredir_channel;
+};
+
+#endif /* FREERDP_CHANNEL_GFXREDIR_SERVER_MAIN_H */
diff --git a/channels/location/CMakeLists.txt b/channels/location/CMakeLists.txt
new file mode 100644
index 0000000..5e77fcb
--- /dev/null
+++ b/channels/location/CMakeLists.txt
@@ -0,0 +1,22 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2023 Pascal Nowack <Pascal.Nowack@gmx.de>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("location")
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/location/ChannelOptions.cmake b/channels/location/ChannelOptions.cmake
new file mode 100644
index 0000000..11f5e30
--- /dev/null
+++ b/channels/location/ChannelOptions.cmake
@@ -0,0 +1,12 @@
+
+set(OPTION_DEFAULT ON)
+set(OPTION_CLIENT_DEFAULT OFF)
+set(OPTION_SERVER_DEFAULT ON)
+
+define_channel_options(NAME "location" TYPE "dynamic"
+ DESCRIPTION "Location Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPEL]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/location/server/CMakeLists.txt b/channels/location/server/CMakeLists.txt
new file mode 100644
index 0000000..d37dc75
--- /dev/null
+++ b/channels/location/server/CMakeLists.txt
@@ -0,0 +1,28 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2023 Pascal Nowack <Pascal.Nowack@gmx.de>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_server("location")
+
+set(${MODULE_PREFIX}_SRCS
+ location_main.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry")
diff --git a/channels/location/server/location_main.c b/channels/location/server/location_main.c
new file mode 100644
index 0000000..bea88ed
--- /dev/null
+++ b/channels/location/server/location_main.c
@@ -0,0 +1,634 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Location Virtual Channel Extension
+ *
+ * Copyright 2023 Pascal Nowack <Pascal.Nowack@gmx.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/server/location.h>
+#include <freerdp/utils/encoded_types.h>
+
+#define TAG CHANNELS_TAG("location.server")
+
+typedef enum
+{
+ LOCATION_INITIAL,
+ LOCATION_OPENED,
+} eLocationChannelState;
+
+typedef struct
+{
+ LocationServerContext context;
+
+ HANDLE stopEvent;
+
+ HANDLE thread;
+ void* location_channel;
+
+ DWORD SessionId;
+
+ BOOL isOpened;
+ BOOL externalThread;
+
+ /* Channel state */
+ eLocationChannelState state;
+
+ wStream* buffer;
+} location_server;
+
+static UINT location_server_initialize(LocationServerContext* context, BOOL externalThread)
+{
+ UINT error = CHANNEL_RC_OK;
+ location_server* location = (location_server*)context;
+
+ WINPR_ASSERT(location);
+
+ if (location->isOpened)
+ {
+ WLog_WARN(TAG, "Application error: Location channel already initialized, "
+ "calling in this state is not possible!");
+ return ERROR_INVALID_STATE;
+ }
+
+ location->externalThread = externalThread;
+
+ return error;
+}
+
+static UINT location_server_open_channel(location_server* location)
+{
+ LocationServerContext* context = &location->context;
+ DWORD Error = ERROR_SUCCESS;
+ HANDLE hEvent = NULL;
+ DWORD BytesReturned = 0;
+ PULONG pSessionId = NULL;
+ UINT32 channelId = 0;
+ BOOL status = TRUE;
+
+ WINPR_ASSERT(location);
+
+ if (WTSQuerySessionInformationA(location->context.vcm, WTS_CURRENT_SESSION, WTSSessionId,
+ (LPSTR*)&pSessionId, &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSQuerySessionInformationA failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ location->SessionId = (DWORD)*pSessionId;
+ WTSFreeMemory(pSessionId);
+ hEvent = WTSVirtualChannelManagerGetEventHandle(location->context.vcm);
+
+ if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED)
+ {
+ Error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error);
+ return Error;
+ }
+
+ location->location_channel = WTSVirtualChannelOpenEx(
+ location->SessionId, LOCATION_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC);
+ if (!location->location_channel)
+ {
+ Error = GetLastError();
+ WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", Error);
+ return Error;
+ }
+
+ channelId = WTSChannelGetIdByHandle(location->location_channel);
+
+ IFCALLRET(context->ChannelIdAssigned, status, context, channelId);
+ if (!status)
+ {
+ WLog_ERR(TAG, "context->ChannelIdAssigned failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return Error;
+}
+
+static UINT location_server_recv_client_ready(LocationServerContext* context, wStream* s,
+ const RDPLOCATION_HEADER* header)
+{
+ RDPLOCATION_CLIENT_READY_PDU pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ pdu.header = *header;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT32(s, pdu.protocolVersion);
+
+ if (Stream_GetRemainingLength(s) >= 4)
+ Stream_Read_UINT32(s, pdu.flags);
+
+ IFCALLRET(context->ClientReady, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->ClientReady failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT location_server_recv_base_location3d(LocationServerContext* context, wStream* s,
+ const RDPLOCATION_HEADER* header)
+{
+ RDPLOCATION_BASE_LOCATION3D_PDU pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+ double speed = 0.0;
+ double heading = 0.0;
+ double horizontalAccuracy = 0.0;
+ LOCATIONSOURCE source = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ pdu.header = *header;
+
+ if (!freerdp_read_four_byte_float(s, &pdu.latitude) ||
+ !freerdp_read_four_byte_float(s, &pdu.longitude) ||
+ !freerdp_read_four_byte_signed_integer(s, &pdu.altitude))
+ return FALSE;
+
+ if (Stream_GetRemainingLength(s) >= 1)
+ {
+ if (!freerdp_read_four_byte_float(s, &speed) ||
+ !freerdp_read_four_byte_float(s, &heading) ||
+ !freerdp_read_four_byte_float(s, &horizontalAccuracy) ||
+ !Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, source);
+
+ pdu.speed = &speed;
+ pdu.heading = &heading;
+ pdu.horizontalAccuracy = &horizontalAccuracy;
+ pdu.source = &source;
+ }
+
+ IFCALLRET(context->BaseLocation3D, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->BaseLocation3D failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT location_server_recv_location2d_delta(LocationServerContext* context, wStream* s,
+ const RDPLOCATION_HEADER* header)
+{
+ RDPLOCATION_LOCATION2D_DELTA_PDU pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+ double speedDelta = 0.0;
+ double headingDelta = 0.0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ pdu.header = *header;
+
+ if (!freerdp_read_four_byte_float(s, &pdu.latitudeDelta) ||
+ !freerdp_read_four_byte_float(s, &pdu.longitudeDelta))
+ return FALSE;
+
+ if (Stream_GetRemainingLength(s) >= 1)
+ {
+ if (!freerdp_read_four_byte_float(s, &speedDelta) ||
+ !freerdp_read_four_byte_float(s, &headingDelta))
+ return FALSE;
+
+ pdu.speedDelta = &speedDelta;
+ pdu.headingDelta = &headingDelta;
+ }
+
+ IFCALLRET(context->Location2DDelta, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->Location2DDelta failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT location_server_recv_location3d_delta(LocationServerContext* context, wStream* s,
+ const RDPLOCATION_HEADER* header)
+{
+ RDPLOCATION_LOCATION3D_DELTA_PDU pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+ double speedDelta = 0.0;
+ double headingDelta = 0.0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ pdu.header = *header;
+
+ if (!freerdp_read_four_byte_float(s, &pdu.latitudeDelta) ||
+ !freerdp_read_four_byte_float(s, &pdu.longitudeDelta) ||
+ !freerdp_read_four_byte_signed_integer(s, &pdu.altitudeDelta))
+ return FALSE;
+
+ if (Stream_GetRemainingLength(s) >= 1)
+ {
+ if (!freerdp_read_four_byte_float(s, &speedDelta) ||
+ !freerdp_read_four_byte_float(s, &headingDelta))
+ return FALSE;
+
+ pdu.speedDelta = &speedDelta;
+ pdu.headingDelta = &headingDelta;
+ }
+
+ IFCALLRET(context->Location3DDelta, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->Location3DDelta failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT location_process_message(location_server* location)
+{
+ BOOL rc = 0;
+ UINT error = ERROR_INTERNAL_ERROR;
+ ULONG BytesReturned = 0;
+ RDPLOCATION_HEADER header = { 0 };
+ wStream* s = NULL;
+
+ WINPR_ASSERT(location);
+ WINPR_ASSERT(location->location_channel);
+
+ s = location->buffer;
+ WINPR_ASSERT(s);
+
+ Stream_SetPosition(s, 0);
+ rc = WTSVirtualChannelRead(location->location_channel, 0, NULL, 0, &BytesReturned);
+ if (!rc)
+ goto out;
+
+ if (BytesReturned < 1)
+ {
+ error = CHANNEL_RC_OK;
+ goto out;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ if (WTSVirtualChannelRead(location->location_channel, 0, (PCHAR)Stream_Buffer(s),
+ (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ goto out;
+ }
+
+ Stream_SetLength(s, BytesReturned);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, LOCATION_HEADER_SIZE))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT16(s, header.pduType);
+ Stream_Read_UINT32(s, header.pduLength);
+
+ switch (header.pduType)
+ {
+ case PDUTYPE_CLIENT_READY:
+ error = location_server_recv_client_ready(&location->context, s, &header);
+ break;
+ case PDUTYPE_BASE_LOCATION3D:
+ error = location_server_recv_base_location3d(&location->context, s, &header);
+ break;
+ case PDUTYPE_LOCATION2D_DELTA:
+ error = location_server_recv_location2d_delta(&location->context, s, &header);
+ break;
+ case PDUTYPE_LOCATION3D_DELTA:
+ error = location_server_recv_location3d_delta(&location->context, s, &header);
+ break;
+ default:
+ WLog_ERR(TAG, "location_process_message: unknown or invalid pduType %" PRIu8 "",
+ header.pduType);
+ break;
+ }
+
+out:
+ if (error)
+ WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+static UINT location_server_context_poll_int(LocationServerContext* context)
+{
+ location_server* location = (location_server*)context;
+ UINT error = ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(location);
+
+ switch (location->state)
+ {
+ case LOCATION_INITIAL:
+ error = location_server_open_channel(location);
+ if (error)
+ WLog_ERR(TAG, "location_server_open_channel failed with error %" PRIu32 "!", error);
+ else
+ location->state = LOCATION_OPENED;
+ break;
+ case LOCATION_OPENED:
+ error = location_process_message(location);
+ break;
+ }
+
+ return error;
+}
+
+static HANDLE location_server_get_channel_handle(location_server* location)
+{
+ void* buffer = NULL;
+ DWORD BytesReturned = 0;
+ HANDLE ChannelEvent = NULL;
+
+ WINPR_ASSERT(location);
+
+ if (WTSVirtualChannelQuery(location->location_channel, WTSVirtualEventHandle, &buffer,
+ &BytesReturned) == TRUE)
+ {
+ if (BytesReturned == sizeof(HANDLE))
+ CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE));
+
+ WTSFreeMemory(buffer);
+ }
+
+ return ChannelEvent;
+}
+
+static DWORD WINAPI location_server_thread_func(LPVOID arg)
+{
+ DWORD nCount = 0;
+ HANDLE events[2] = { 0 };
+ location_server* location = (location_server*)arg;
+ UINT error = CHANNEL_RC_OK;
+ DWORD status = 0;
+
+ WINPR_ASSERT(location);
+
+ nCount = 0;
+ events[nCount++] = location->stopEvent;
+
+ while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0))
+ {
+ switch (location->state)
+ {
+ case LOCATION_INITIAL:
+ error = location_server_context_poll_int(&location->context);
+ if (error == CHANNEL_RC_OK)
+ {
+ events[1] = location_server_get_channel_handle(location);
+ nCount = 2;
+ }
+ break;
+ case LOCATION_OPENED:
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+ switch (status)
+ {
+ case WAIT_OBJECT_0:
+ break;
+ case WAIT_OBJECT_0 + 1:
+ case WAIT_TIMEOUT:
+ error = location_server_context_poll_int(&location->context);
+ break;
+
+ case WAIT_FAILED:
+ default:
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+ break;
+ }
+ }
+
+ WTSVirtualChannelClose(location->location_channel);
+ location->location_channel = NULL;
+
+ if (error && location->context.rdpcontext)
+ setChannelError(location->context.rdpcontext, error,
+ "location_server_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+static UINT location_server_open(LocationServerContext* context)
+{
+ location_server* location = (location_server*)context;
+
+ WINPR_ASSERT(location);
+
+ if (!location->externalThread && (location->thread == NULL))
+ {
+ location->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!location->stopEvent)
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ location->thread = CreateThread(NULL, 0, location_server_thread_func, location, 0, NULL);
+ if (!location->thread)
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ CloseHandle(location->stopEvent);
+ location->stopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+ location->isOpened = TRUE;
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT location_server_close(LocationServerContext* context)
+{
+ UINT error = CHANNEL_RC_OK;
+ location_server* location = (location_server*)context;
+
+ WINPR_ASSERT(location);
+
+ if (!location->externalThread && location->thread)
+ {
+ SetEvent(location->stopEvent);
+
+ if (WaitForSingleObject(location->thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ CloseHandle(location->thread);
+ CloseHandle(location->stopEvent);
+ location->thread = NULL;
+ location->stopEvent = NULL;
+ }
+ if (location->externalThread)
+ {
+ if (location->state != LOCATION_INITIAL)
+ {
+ WTSVirtualChannelClose(location->location_channel);
+ location->location_channel = NULL;
+ location->state = LOCATION_INITIAL;
+ }
+ }
+ location->isOpened = FALSE;
+
+ return error;
+}
+
+static UINT location_server_context_poll(LocationServerContext* context)
+{
+ location_server* location = (location_server*)context;
+
+ WINPR_ASSERT(location);
+
+ if (!location->externalThread)
+ return ERROR_INTERNAL_ERROR;
+
+ return location_server_context_poll_int(context);
+}
+
+static BOOL location_server_context_handle(LocationServerContext* context, HANDLE* handle)
+{
+ location_server* location = (location_server*)context;
+
+ WINPR_ASSERT(location);
+ WINPR_ASSERT(handle);
+
+ if (!location->externalThread)
+ return FALSE;
+ if (location->state == LOCATION_INITIAL)
+ return FALSE;
+
+ *handle = location_server_get_channel_handle(location);
+
+ return TRUE;
+}
+
+static UINT location_server_packet_send(LocationServerContext* context, wStream* s)
+{
+ location_server* location = (location_server*)context;
+ UINT error = CHANNEL_RC_OK;
+ ULONG written = 0;
+
+ WINPR_ASSERT(location);
+ WINPR_ASSERT(s);
+
+ if (!WTSVirtualChannelWrite(location->location_channel, (PCHAR)Stream_Buffer(s),
+ Stream_GetPosition(s), &written))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ if (written < Stream_GetPosition(s))
+ {
+ WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written,
+ Stream_GetPosition(s));
+ }
+
+out:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+static UINT location_server_send_server_ready(LocationServerContext* context,
+ const RDPLOCATION_SERVER_READY_PDU* serverReady)
+{
+ wStream* s = NULL;
+ UINT32 pduLength = 0;
+ UINT32 protocolVersion = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(serverReady);
+
+ protocolVersion = serverReady->protocolVersion;
+
+ pduLength = LOCATION_HEADER_SIZE + 4 + 4;
+
+ s = Stream_New(NULL, pduLength);
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* RDPLOCATION_HEADER */
+ Stream_Write_UINT16(s, PDUTYPE_SERVER_READY);
+ Stream_Write_UINT32(s, pduLength);
+
+ Stream_Write_UINT32(s, protocolVersion);
+ Stream_Write_UINT32(s, serverReady->flags);
+
+ return location_server_packet_send(context, s);
+}
+
+LocationServerContext* location_server_context_new(HANDLE vcm)
+{
+ location_server* location = (location_server*)calloc(1, sizeof(location_server));
+
+ if (!location)
+ return NULL;
+
+ location->context.vcm = vcm;
+ location->context.Initialize = location_server_initialize;
+ location->context.Open = location_server_open;
+ location->context.Close = location_server_close;
+ location->context.Poll = location_server_context_poll;
+ location->context.ChannelHandle = location_server_context_handle;
+
+ location->context.ServerReady = location_server_send_server_ready;
+
+ location->buffer = Stream_New(NULL, 4096);
+ if (!location->buffer)
+ goto fail;
+
+ return &location->context;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ location_server_context_free(&location->context);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void location_server_context_free(LocationServerContext* context)
+{
+ location_server* location = (location_server*)context;
+
+ if (location)
+ {
+ location_server_close(context);
+ Stream_Free(location->buffer, TRUE);
+ }
+
+ free(location);
+}
diff --git a/channels/parallel/CMakeLists.txt b/channels/parallel/CMakeLists.txt
new file mode 100644
index 0000000..0faabb5
--- /dev/null
+++ b/channels/parallel/CMakeLists.txt
@@ -0,0 +1,22 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("parallel")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/parallel/ChannelOptions.cmake b/channels/parallel/ChannelOptions.cmake
new file mode 100644
index 0000000..42a8669
--- /dev/null
+++ b/channels/parallel/ChannelOptions.cmake
@@ -0,0 +1,23 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT OFF)
+
+if(WIN32)
+ set(OPTION_CLIENT_DEFAULT OFF)
+ set(OPTION_SERVER_DEFAULT OFF)
+endif()
+
+if(ANDROID)
+ set(OPTION_CLIENT_DEFAULT OFF)
+ set(OPTION_SERVER_DEFAULT OFF)
+endif()
+
+define_channel_options(NAME "parallel" TYPE "device"
+ DESCRIPTION "Parallel Port Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPESP]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/parallel/client/CMakeLists.txt b/channels/parallel/client/CMakeLists.txt
new file mode 100644
index 0000000..d783ead
--- /dev/null
+++ b/channels/parallel/client/CMakeLists.txt
@@ -0,0 +1,27 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("parallel")
+
+set(${MODULE_PREFIX}_SRCS
+ parallel_main.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr freerdp
+)
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DeviceServiceEntry")
diff --git a/channels/parallel/client/parallel_main.c b/channels/parallel/client/parallel_main.c
new file mode 100644
index 0000000..f0801e1
--- /dev/null
+++ b/channels/parallel/client/parallel_main.c
@@ -0,0 +1,503 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Redirected Parallel Port Device Service
+ *
+ * Copyright 2010 O.S. Systems Software Ltda.
+ * Copyright 2010 Eduardo Fiss Beloni <beloni@ossystems.com.br>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <fcntl.h>
+#include <errno.h>
+
+#ifndef _WIN32
+#include <termios.h>
+#include <strings.h>
+#include <sys/ioctl.h>
+#endif
+
+#ifdef __LINUX__
+#include <linux/ppdev.h>
+#include <linux/parport.h>
+#endif
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+#include <winpr/interlocked.h>
+
+#include <freerdp/types.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/constants.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("drive.client")
+
+typedef struct
+{
+ DEVICE device;
+
+ int file;
+ char* path;
+ UINT32 id;
+
+ HANDLE thread;
+ wMessageQueue* queue;
+ rdpContext* rdpcontext;
+} PARALLEL_DEVICE;
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT parallel_process_irp_create(PARALLEL_DEVICE* parallel, IRP* irp)
+{
+ char* path = NULL;
+ UINT32 PathLength = 0;
+ if (!Stream_SafeSeek(irp->input, 28))
+ return ERROR_INVALID_DATA;
+ /* DesiredAccess(4) AllocationSize(8), FileAttributes(4) */
+ /* SharedAccess(4) CreateDisposition(4), CreateOptions(4) */
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 4))
+ return ERROR_INVALID_DATA;
+ Stream_Read_UINT32(irp->input, PathLength);
+ if (PathLength < sizeof(WCHAR))
+ return ERROR_INVALID_DATA;
+ const WCHAR* ptr = Stream_ConstPointer(irp->input);
+ if (!Stream_SafeSeek(irp->input, PathLength))
+ return ERROR_INVALID_DATA;
+ path = ConvertWCharNToUtf8Alloc(ptr, PathLength / sizeof(WCHAR), NULL);
+ if (!path)
+ return CHANNEL_RC_NO_MEMORY;
+
+ parallel->id = irp->devman->id_sequence++;
+ parallel->file = open(parallel->path, O_RDWR);
+
+ if (parallel->file < 0)
+ {
+ irp->IoStatus = STATUS_ACCESS_DENIED;
+ parallel->id = 0;
+ }
+ else
+ {
+ /* all read and write operations should be non-blocking */
+ if (fcntl(parallel->file, F_SETFL, O_NONBLOCK) == -1)
+ {
+ }
+ }
+
+ Stream_Write_UINT32(irp->output, parallel->id);
+ Stream_Write_UINT8(irp->output, 0);
+ free(path);
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT parallel_process_irp_close(PARALLEL_DEVICE* parallel, IRP* irp)
+{
+ if (close(parallel->file) < 0)
+ {
+ }
+ else
+ {
+ }
+
+ Stream_Zero(irp->output, 5); /* Padding(5) */
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT parallel_process_irp_read(PARALLEL_DEVICE* parallel, IRP* irp)
+{
+ UINT32 Length = 0;
+ UINT64 Offset = 0;
+ ssize_t status = 0;
+ BYTE* buffer = NULL;
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 12))
+ return ERROR_INVALID_DATA;
+ Stream_Read_UINT32(irp->input, Length);
+ Stream_Read_UINT64(irp->input, Offset);
+ buffer = (BYTE*)calloc(Length, sizeof(BYTE));
+
+ if (!buffer)
+ {
+ WLog_ERR(TAG, "malloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ status = read(parallel->file, buffer, Length);
+
+ if (status < 0)
+ {
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ free(buffer);
+ buffer = NULL;
+ Length = 0;
+ }
+ else
+ {
+ Length = status;
+ }
+
+ Stream_Write_UINT32(irp->output, Length);
+
+ if (Length > 0)
+ {
+ if (!Stream_EnsureRemainingCapacity(irp->output, Length))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ free(buffer);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write(irp->output, buffer, Length);
+ }
+
+ free(buffer);
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT parallel_process_irp_write(PARALLEL_DEVICE* parallel, IRP* irp)
+{
+ UINT32 len = 0;
+ UINT32 Length = 0;
+ UINT64 Offset = 0;
+ ssize_t status = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, Length);
+ Stream_Read_UINT64(irp->input, Offset);
+ if (!Stream_SafeSeek(irp->input, 20)) /* Padding */
+ return ERROR_INVALID_DATA;
+ const void* ptr = Stream_ConstPointer(irp->input);
+ if (!Stream_SafeSeek(irp->input, Length))
+ return ERROR_INVALID_DATA;
+ len = Length;
+
+ while (len > 0)
+ {
+ status = write(parallel->file, ptr, len);
+
+ if (status < 0)
+ {
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ Length = 0;
+ break;
+ }
+
+ Stream_Seek(irp->input, status);
+ len -= status;
+ }
+
+ Stream_Write_UINT32(irp->output, Length);
+ Stream_Write_UINT8(irp->output, 0); /* Padding */
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT parallel_process_irp_device_control(PARALLEL_DEVICE* parallel, IRP* irp)
+{
+ Stream_Write_UINT32(irp->output, 0); /* OutputBufferLength */
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT parallel_process_irp(PARALLEL_DEVICE* parallel, IRP* irp)
+{
+ UINT error = 0;
+
+ switch (irp->MajorFunction)
+ {
+ case IRP_MJ_CREATE:
+ if ((error = parallel_process_irp_create(parallel, irp)))
+ {
+ WLog_ERR(TAG, "parallel_process_irp_create failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ break;
+
+ case IRP_MJ_CLOSE:
+ if ((error = parallel_process_irp_close(parallel, irp)))
+ {
+ WLog_ERR(TAG, "parallel_process_irp_close failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ break;
+
+ case IRP_MJ_READ:
+ if ((error = parallel_process_irp_read(parallel, irp)))
+ {
+ WLog_ERR(TAG, "parallel_process_irp_read failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ break;
+
+ case IRP_MJ_WRITE:
+ if ((error = parallel_process_irp_write(parallel, irp)))
+ {
+ WLog_ERR(TAG, "parallel_process_irp_write failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ break;
+
+ case IRP_MJ_DEVICE_CONTROL:
+ if ((error = parallel_process_irp_device_control(parallel, irp)))
+ {
+ WLog_ERR(TAG, "parallel_process_irp_device_control failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ default:
+ irp->IoStatus = STATUS_NOT_SUPPORTED;
+ return irp->Complete(irp);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static DWORD WINAPI parallel_thread_func(LPVOID arg)
+{
+ IRP* irp = NULL;
+ wMessage message = { 0 };
+ PARALLEL_DEVICE* parallel = (PARALLEL_DEVICE*)arg;
+ UINT error = CHANNEL_RC_OK;
+
+ while (1)
+ {
+ if (!MessageQueue_Wait(parallel->queue))
+ {
+ WLog_ERR(TAG, "MessageQueue_Wait failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (!MessageQueue_Peek(parallel->queue, &message, TRUE))
+ {
+ WLog_ERR(TAG, "MessageQueue_Peek failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (message.id == WMQ_QUIT)
+ break;
+
+ irp = (IRP*)message.wParam;
+
+ if ((error = parallel_process_irp(parallel, irp)))
+ {
+ WLog_ERR(TAG, "parallel_process_irp failed with error %" PRIu32 "!", error);
+ break;
+ }
+ }
+
+ if (error && parallel->rdpcontext)
+ setChannelError(parallel->rdpcontext, error, "parallel_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT parallel_irp_request(DEVICE* device, IRP* irp)
+{
+ PARALLEL_DEVICE* parallel = (PARALLEL_DEVICE*)device;
+
+ if (!MessageQueue_Post(parallel->queue, NULL, 0, (void*)irp, NULL))
+ {
+ WLog_ERR(TAG, "MessageQueue_Post failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT parallel_free(DEVICE* device)
+{
+ UINT error = 0;
+ PARALLEL_DEVICE* parallel = (PARALLEL_DEVICE*)device;
+
+ if (!MessageQueue_PostQuit(parallel->queue, 0) ||
+ (WaitForSingleObject(parallel->thread, INFINITE) == WAIT_FAILED))
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ CloseHandle(parallel->thread);
+ Stream_Free(parallel->device.data, TRUE);
+ MessageQueue_Free(parallel->queue);
+ free(parallel);
+ return CHANNEL_RC_OK;
+}
+
+static void parallel_message_free(void* obj)
+{
+ wMessage* msg = obj;
+ if (!msg)
+ return;
+ if (msg->id != 0)
+ return;
+
+ IRP* irp = (IRP*)msg->wParam;
+ if (!irp)
+ return;
+ WINPR_ASSERT(irp->Discard);
+ irp->Discard(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT parallel_DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints))
+{
+ char* name = NULL;
+ char* path = NULL;
+ size_t length = 0;
+ RDPDR_PARALLEL* device = NULL;
+ PARALLEL_DEVICE* parallel = NULL;
+ UINT error = 0;
+
+ WINPR_ASSERT(pEntryPoints);
+
+ device = (RDPDR_PARALLEL*)pEntryPoints->device;
+ WINPR_ASSERT(device);
+
+ name = device->device.Name;
+ path = device->Path;
+
+ if (!name || (name[0] == '*') || !path)
+ {
+ /* TODO: implement auto detection of parallel ports */
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+ }
+
+ if (name[0] && path[0])
+ {
+ parallel = (PARALLEL_DEVICE*)calloc(1, sizeof(PARALLEL_DEVICE));
+
+ if (!parallel)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ parallel->device.type = RDPDR_DTYP_PARALLEL;
+ parallel->device.name = name;
+ parallel->device.IRPRequest = parallel_irp_request;
+ parallel->device.Free = parallel_free;
+ parallel->rdpcontext = pEntryPoints->rdpcontext;
+ length = strlen(name);
+ parallel->device.data = Stream_New(NULL, length + 1);
+
+ if (!parallel->device.data)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+
+ for (size_t i = 0; i <= length; i++)
+ Stream_Write_UINT8(parallel->device.data, name[i] < 0 ? '_' : name[i]);
+
+ parallel->path = path;
+ parallel->queue = MessageQueue_New(NULL);
+
+ if (!parallel->queue)
+ {
+ WLog_ERR(TAG, "MessageQueue_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+
+ wObject* obj = MessageQueue_Object(parallel->queue);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = parallel_message_free;
+
+ if ((error = pEntryPoints->RegisterDevice(pEntryPoints->devman, (DEVICE*)parallel)))
+ {
+ WLog_ERR(TAG, "RegisterDevice failed with error %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ if (!(parallel->thread =
+ CreateThread(NULL, 0, parallel_thread_func, (void*)parallel, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto error_out;
+ }
+ }
+
+ return CHANNEL_RC_OK;
+error_out:
+ MessageQueue_Free(parallel->queue);
+ Stream_Free(parallel->device.data, TRUE);
+ free(parallel);
+ return error;
+}
diff --git a/channels/printer/CMakeLists.txt b/channels/printer/CMakeLists.txt
new file mode 100644
index 0000000..73cb415
--- /dev/null
+++ b/channels/printer/CMakeLists.txt
@@ -0,0 +1,22 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("printer")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif() \ No newline at end of file
diff --git a/channels/printer/ChannelOptions.cmake b/channels/printer/ChannelOptions.cmake
new file mode 100644
index 0000000..2989ba5
--- /dev/null
+++ b/channels/printer/ChannelOptions.cmake
@@ -0,0 +1,33 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT OFF)
+
+if(WIN32)
+ set(OPTION_CLIENT_DEFAULT ON)
+ set(OPTION_SERVER_DEFAULT OFF)
+else()
+ # cups is available on mac os and linux by default, on android it is optional.
+ if (NOT IOS AND NOT ANDROID)
+ set(CUPS_DEFAULT ON)
+ else()
+ set(CUPS_DEFAULT OFF)
+ endif()
+ option(WITH_CUPS "CUPS printer support" ${CUPS_DEFAULT})
+ if(WITH_CUPS)
+ set(OPTION_CLIENT_DEFAULT ON)
+ set(OPTION_SERVER_DEFAULT OFF)
+ else()
+ set(OPTION_CLIENT_DEFAULT OFF)
+ set(OPTION_SERVER_DEFAULT OFF)
+ endif()
+endif()
+
+define_channel_options(NAME "printer" TYPE "device"
+ DESCRIPTION "Print Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPEPC]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/printer/client/CMakeLists.txt b/channels/printer/client/CMakeLists.txt
new file mode 100644
index 0000000..e7bab09
--- /dev/null
+++ b/channels/printer/client/CMakeLists.txt
@@ -0,0 +1,35 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("printer")
+
+set(${MODULE_PREFIX}_SRCS
+ printer_main.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr freerdp
+)
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DeviceServiceEntry")
+
+if(WITH_CUPS)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "cups" "")
+endif()
+
+if(WIN32 AND NOT UWP)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "win" "")
+endif()
diff --git a/channels/printer/client/cups/CMakeLists.txt b/channels/printer/client/cups/CMakeLists.txt
new file mode 100644
index 0000000..c6b6f0e
--- /dev/null
+++ b/channels/printer/client/cups/CMakeLists.txt
@@ -0,0 +1,33 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2019 Armin Novak <armin.novak@thincast.com>
+# Copyright 2019 Thincast Technologies GmbH
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+define_channel_client_subsystem("printer" "cups" "")
+
+find_package(Cups 2.0 REQUIRED)
+set(${MODULE_PREFIX}_SRCS
+ printer_cups.c)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${CUPS_LIBRARIES}
+)
+
+include_directories(..)
+include_directories(${CUPS_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/printer/client/cups/printer_cups.c b/channels/printer/client/cups/printer_cups.c
new file mode 100644
index 0000000..043b44e
--- /dev/null
+++ b/channels/printer/client/cups/printer_cups.c
@@ -0,0 +1,462 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Print Virtual Channel - CUPS driver
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 Armin Novak <armin.novak@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <winpr/assert.h>
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+
+#include <time.h>
+#include <cups/cups.h>
+
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/string.h>
+
+#include <freerdp/channels/rdpdr.h>
+
+#include <freerdp/client/printer.h>
+
+#include <freerdp/channels/log.h>
+#define TAG CHANNELS_TAG("printer.client.cups")
+
+#if defined(__APPLE__)
+#include <errno.h>
+#include <sys/sysctl.h>
+
+static bool is_mac_os_sonoma_or_later(void)
+{
+ char str[256] = { 0 };
+ size_t size = sizeof(str);
+
+ errno = 0;
+ int ret = sysctlbyname("kern.osrelease", str, &size, NULL, 0);
+ if (ret != 0)
+ {
+ char buffer[256] = { 0 };
+ WLog_WARN(TAG, "sysctlbyname('kern.osrelease') failed with %s [%d]",
+ winpr_strerror(errno, buffer, sizeof(buffer)), errno);
+ return false;
+ }
+
+ int major = 0;
+ int minor = 0;
+ int patch = 0;
+ const int rc = sscanf(str, "%d.%d.%d", &major, &minor, &patch);
+ if (rc != 3)
+ {
+ WLog_WARN(TAG, "could not match '%s' to format '%d.%d.%d'");
+ return false;
+ }
+
+ if (major < 23)
+ return false;
+ return true;
+}
+#endif
+
+typedef struct
+{
+ rdpPrinterDriver driver;
+
+ size_t id_sequence;
+ size_t references;
+} rdpCupsPrinterDriver;
+
+typedef struct
+{
+ rdpPrintJob printjob;
+
+ http_t* printjob_object;
+ int printjob_id;
+} rdpCupsPrintJob;
+
+typedef struct
+{
+ rdpPrinter printer;
+
+ rdpCupsPrintJob* printjob;
+} rdpCupsPrinter;
+
+static void printer_cups_get_printjob_name(char* buf, size_t size, size_t id)
+{
+ struct tm tres;
+ const time_t tt = time(NULL);
+ const struct tm* t = localtime_r(&tt, &tres);
+
+ WINPR_ASSERT(buf);
+ WINPR_ASSERT(size > 0);
+
+ sprintf_s(buf, size - 1, "FreeRDP Print %04d-%02d-%02d %02d-%02d-%02d - Job %" PRIdz,
+ t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, id);
+}
+
+static bool http_status_ok(http_status_t status)
+{
+ switch (status)
+ {
+ case HTTP_OK:
+ case HTTP_CONTINUE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static UINT write_printjob(rdpPrintJob* printjob, const BYTE* data, size_t size)
+{
+ rdpCupsPrintJob* cups_printjob = (rdpCupsPrintJob*)printjob;
+
+ WINPR_ASSERT(cups_printjob);
+
+ http_status_t rc =
+ cupsWriteRequestData(cups_printjob->printjob_object, (const char*)data, size);
+ if (!http_status_ok(rc))
+ WLog_WARN(TAG, "cupsWriteRequestData returned %s", httpStatus(rc));
+
+ return CHANNEL_RC_OK;
+}
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT printer_cups_write_printjob(rdpPrintJob* printjob, const BYTE* data, size_t size)
+{
+ rdpCupsPrintJob* cups_printjob = (rdpCupsPrintJob*)printjob;
+
+ WINPR_ASSERT(cups_printjob);
+
+ return write_printjob(printjob, data, size);
+}
+
+static void printer_cups_close_printjob(rdpPrintJob* printjob)
+{
+ rdpCupsPrintJob* cups_printjob = (rdpCupsPrintJob*)printjob;
+ rdpCupsPrinter* cups_printer = NULL;
+
+ WINPR_ASSERT(cups_printjob);
+
+ ipp_status_t rc = cupsFinishDocument(cups_printjob->printjob_object, printjob->printer->name);
+ if (rc != IPP_OK)
+ WLog_WARN(TAG, "cupsFinishDocument returned %s", ippErrorString(rc));
+
+ cups_printjob->printjob_id = 0;
+ httpClose(cups_printjob->printjob_object);
+
+ cups_printer = (rdpCupsPrinter*)printjob->printer;
+ WINPR_ASSERT(cups_printer);
+
+ cups_printer->printjob = NULL;
+ free(cups_printjob);
+}
+
+static rdpPrintJob* printer_cups_create_printjob(rdpPrinter* printer, UINT32 id)
+{
+ rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer;
+ rdpCupsPrintJob* cups_printjob = NULL;
+
+ WINPR_ASSERT(cups_printer);
+
+ if (cups_printer->printjob != NULL)
+ {
+ WLog_WARN(TAG, "printjob [printer '%s'] already existing, abort!", printer->name);
+ return NULL;
+ }
+
+ cups_printjob = (rdpCupsPrintJob*)calloc(1, sizeof(rdpCupsPrintJob));
+ if (!cups_printjob)
+ return NULL;
+
+ cups_printjob->printjob.id = id;
+ cups_printjob->printjob.printer = printer;
+
+ cups_printjob->printjob.Write = printer_cups_write_printjob;
+ cups_printjob->printjob.Close = printer_cups_close_printjob;
+
+ {
+ char buf[100] = { 0 };
+
+ cups_printjob->printjob_object = httpConnect2(cupsServer(), ippPort(), NULL, AF_UNSPEC,
+ HTTP_ENCRYPT_IF_REQUESTED, 1, 10000, NULL);
+
+ if (!cups_printjob->printjob_object)
+ {
+ WLog_WARN(TAG, "httpConnect2 failed for '%s:%d", cupsServer(), ippPort());
+ free(cups_printjob);
+ return NULL;
+ }
+
+ printer_cups_get_printjob_name(buf, sizeof(buf), cups_printjob->printjob.id);
+
+ cups_printjob->printjob_id =
+ cupsCreateJob(cups_printjob->printjob_object, printer->name, buf, 0, NULL);
+
+ if (!cups_printjob->printjob_id)
+ {
+ WLog_WARN(TAG, "cupsCreateJob failed for printer '%s', driver '%s'", printer->name,
+ printer->driver);
+ httpClose(cups_printjob->printjob_object);
+ free(cups_printjob);
+ return NULL;
+ }
+
+ http_status_t rc = cupsStartDocument(cups_printjob->printjob_object, printer->name,
+ cups_printjob->printjob_id, buf, CUPS_FORMAT_AUTO, 1);
+ if (!http_status_ok(rc))
+ WLog_WARN(TAG, "cupsStartDocument [printer '%s', driver '%s'] returned %s",
+ printer->name, printer->driver, httpStatus(rc));
+ }
+
+ cups_printer->printjob = cups_printjob;
+
+ return &cups_printjob->printjob;
+}
+
+static rdpPrintJob* printer_cups_find_printjob(rdpPrinter* printer, UINT32 id)
+{
+ rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer;
+
+ WINPR_ASSERT(cups_printer);
+
+ if (cups_printer->printjob == NULL)
+ return NULL;
+ if (cups_printer->printjob->printjob.id != id)
+ return NULL;
+
+ return &cups_printer->printjob->printjob;
+}
+
+static void printer_cups_free_printer(rdpPrinter* printer)
+{
+ rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer;
+
+ WINPR_ASSERT(cups_printer);
+
+ if (cups_printer->printjob)
+ {
+ WINPR_ASSERT(cups_printer->printjob->printjob.Close);
+ cups_printer->printjob->printjob.Close(&cups_printer->printjob->printjob);
+ }
+
+ if (printer->backend)
+ {
+ WINPR_ASSERT(printer->backend->ReleaseRef);
+ printer->backend->ReleaseRef(printer->backend);
+ }
+ free(printer->name);
+ free(printer->driver);
+ free(printer);
+}
+
+static void printer_cups_add_ref_printer(rdpPrinter* printer)
+{
+ if (printer)
+ printer->references++;
+}
+
+static void printer_cups_release_ref_printer(rdpPrinter* printer)
+{
+ if (!printer)
+ return;
+ if (printer->references <= 1)
+ printer_cups_free_printer(printer);
+ else
+ printer->references--;
+}
+
+static rdpPrinter* printer_cups_new_printer(rdpCupsPrinterDriver* cups_driver, const char* name,
+ const char* driverName, BOOL is_default)
+{
+ rdpCupsPrinter* cups_printer = NULL;
+
+ cups_printer = (rdpCupsPrinter*)calloc(1, sizeof(rdpCupsPrinter));
+ if (!cups_printer)
+ return NULL;
+
+ cups_printer->printer.backend = &cups_driver->driver;
+
+ cups_printer->printer.id = cups_driver->id_sequence++;
+ cups_printer->printer.name = _strdup(name);
+ if (!cups_printer->printer.name)
+ goto fail;
+
+ if (driverName)
+ cups_printer->printer.driver = _strdup(driverName);
+ else
+ {
+ const char* dname = "MS Publisher Imagesetter";
+#if defined(__APPLE__)
+ if (is_mac_os_sonoma_or_later())
+ dname = "Microsoft Print to PDF";
+#endif
+ cups_printer->printer.driver = _strdup(dname);
+ }
+ if (!cups_printer->printer.driver)
+ goto fail;
+
+ cups_printer->printer.is_default = is_default;
+
+ cups_printer->printer.CreatePrintJob = printer_cups_create_printjob;
+ cups_printer->printer.FindPrintJob = printer_cups_find_printjob;
+ cups_printer->printer.AddRef = printer_cups_add_ref_printer;
+ cups_printer->printer.ReleaseRef = printer_cups_release_ref_printer;
+
+ WINPR_ASSERT(cups_printer->printer.AddRef);
+ cups_printer->printer.AddRef(&cups_printer->printer);
+
+ WINPR_ASSERT(cups_printer->printer.backend->AddRef);
+ cups_printer->printer.backend->AddRef(cups_printer->printer.backend);
+
+ return &cups_printer->printer;
+
+fail:
+ printer_cups_free_printer(&cups_printer->printer);
+ return NULL;
+}
+
+static void printer_cups_release_enum_printers(rdpPrinter** printers)
+{
+ rdpPrinter** cur = printers;
+
+ while ((cur != NULL) && ((*cur) != NULL))
+ {
+ if ((*cur)->ReleaseRef)
+ (*cur)->ReleaseRef(*cur);
+ cur++;
+ }
+ free(printers);
+}
+
+static rdpPrinter** printer_cups_enum_printers(rdpPrinterDriver* driver)
+{
+ rdpPrinter** printers = NULL;
+ int num_printers = 0;
+ cups_dest_t* dests = NULL;
+ BOOL haveDefault = FALSE;
+ const int num_dests = cupsGetDests(&dests);
+
+ WINPR_ASSERT(driver);
+ if (num_dests >= 0)
+ printers = (rdpPrinter**)calloc((size_t)num_dests + 1, sizeof(rdpPrinter*));
+ if (!printers)
+ return NULL;
+
+ for (size_t i = 0; i < num_dests; i++)
+ {
+ const cups_dest_t* dest = &dests[i];
+ if (dest->instance == NULL)
+ {
+ rdpPrinter* current = printer_cups_new_printer((rdpCupsPrinterDriver*)driver,
+ dest->name, NULL, dest->is_default);
+ if (!current)
+ {
+ printer_cups_release_enum_printers(printers);
+ printers = NULL;
+ break;
+ }
+
+ if (current->is_default)
+ haveDefault = TRUE;
+
+ printers[num_printers++] = current;
+ }
+ }
+ cupsFreeDests(num_dests, dests);
+
+ if (!haveDefault && (num_dests > 0) && printers)
+ {
+ if (printers[0])
+ printers[0]->is_default = TRUE;
+ }
+
+ return printers;
+}
+
+static rdpPrinter* printer_cups_get_printer(rdpPrinterDriver* driver, const char* name,
+ const char* driverName, BOOL isDefault)
+{
+ rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
+
+ WINPR_ASSERT(cups_driver);
+ return printer_cups_new_printer(cups_driver, name, driverName, isDefault);
+}
+
+static void printer_cups_add_ref_driver(rdpPrinterDriver* driver)
+{
+ rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
+ if (cups_driver)
+ cups_driver->references++;
+}
+
+/* Singleton */
+static rdpCupsPrinterDriver* uniq_cups_driver = NULL;
+
+static void printer_cups_release_ref_driver(rdpPrinterDriver* driver)
+{
+ rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
+
+ WINPR_ASSERT(cups_driver);
+
+ if (cups_driver->references <= 1)
+ {
+ if (uniq_cups_driver == cups_driver)
+ uniq_cups_driver = NULL;
+ free(cups_driver);
+ }
+ else
+ cups_driver->references--;
+}
+
+FREERDP_ENTRY_POINT(UINT cups_freerdp_printer_client_subsystem_entry(void* arg))
+{
+ rdpPrinterDriver** ppPrinter = (rdpPrinterDriver**)arg;
+ if (!ppPrinter)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!uniq_cups_driver)
+ {
+ uniq_cups_driver = (rdpCupsPrinterDriver*)calloc(1, sizeof(rdpCupsPrinterDriver));
+
+ if (!uniq_cups_driver)
+ return ERROR_OUTOFMEMORY;
+
+ uniq_cups_driver->driver.EnumPrinters = printer_cups_enum_printers;
+ uniq_cups_driver->driver.ReleaseEnumPrinters = printer_cups_release_enum_printers;
+ uniq_cups_driver->driver.GetPrinter = printer_cups_get_printer;
+
+ uniq_cups_driver->driver.AddRef = printer_cups_add_ref_driver;
+ uniq_cups_driver->driver.ReleaseRef = printer_cups_release_ref_driver;
+
+ uniq_cups_driver->id_sequence = 1;
+ }
+
+ WINPR_ASSERT(uniq_cups_driver->driver.AddRef);
+ uniq_cups_driver->driver.AddRef(&uniq_cups_driver->driver);
+
+ *ppPrinter = &uniq_cups_driver->driver;
+ return CHANNEL_RC_OK;
+}
diff --git a/channels/printer/client/printer_main.c b/channels/printer/client/printer_main.c
new file mode 100644
index 0000000..2aeb3f4
--- /dev/null
+++ b/channels/printer/client/printer_main.c
@@ -0,0 +1,1159 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Print Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 Armin Novak <armin.novak@gmail.com>
+ * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/string.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/interlocked.h>
+#include <winpr/path.h>
+
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/crypto/crypto.h>
+#include <freerdp/freerdp.h>
+
+#include "../printer.h"
+
+#include <freerdp/client/printer.h>
+
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("printer.client")
+
+typedef struct
+{
+ DEVICE device;
+
+ rdpPrinter* printer;
+
+ WINPR_PSLIST_HEADER pIrpList;
+
+ HANDLE event;
+ HANDLE stopEvent;
+
+ HANDLE thread;
+ rdpContext* rdpcontext;
+ char port[64];
+} PRINTER_DEVICE;
+
+typedef enum
+{
+ PRN_CONF_PORT = 0,
+ PRN_CONF_PNP = 1,
+ PRN_CONF_DRIVER = 2,
+ PRN_CONF_DATA = 3
+} prn_conf_t;
+
+static const char* filemap[] = { "PortDosName", "PnPName", "DriverName",
+ "CachedPrinterConfigData" };
+
+static char* get_printer_config_path(const rdpSettings* settings, const WCHAR* name, size_t length)
+{
+ const char* path = freerdp_settings_get_string(settings, FreeRDP_ConfigPath);
+ char* dir = GetCombinedPath(path, "printers");
+ char* bname = crypto_base64_encode((const BYTE*)name, length);
+ char* config = GetCombinedPath(dir, bname);
+
+ if (config && !winpr_PathFileExists(config))
+ {
+ if (!winpr_PathMakePath(config, NULL))
+ {
+ free(config);
+ config = NULL;
+ }
+ }
+
+ free(dir);
+ free(bname);
+ return config;
+}
+
+static BOOL printer_write_setting(const char* path, prn_conf_t type, const void* data,
+ size_t length)
+{
+ DWORD written = 0;
+ BOOL rc = FALSE;
+ HANDLE file = NULL;
+ size_t b64len = 0;
+ char* base64 = NULL;
+ const char* name = filemap[type];
+ char* abs = GetCombinedPath(path, name);
+
+ if (!abs || (length > INT32_MAX))
+ {
+ free(abs);
+ return FALSE;
+ }
+
+ file = CreateFileA(abs, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ free(abs);
+
+ if (file == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (length > 0)
+ {
+ base64 = crypto_base64_encode(data, length);
+
+ if (!base64)
+ goto fail;
+
+ /* base64 char represents 6bit -> 4*(n/3) is the length which is
+ * always smaller than 2*n */
+ b64len = strnlen(base64, 2 * length);
+ rc = WriteFile(file, base64, b64len, &written, NULL);
+
+ if (b64len != written)
+ rc = FALSE;
+ }
+ else
+ rc = TRUE;
+
+fail:
+ CloseHandle(file);
+ free(base64);
+ return rc;
+}
+
+static BOOL printer_config_valid(const char* path)
+{
+ if (!path)
+ return FALSE;
+
+ if (!winpr_PathFileExists(path))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL printer_read_setting(const char* path, prn_conf_t type, void** data, UINT32* length)
+{
+ DWORD lowSize = 0;
+ DWORD highSize = 0;
+ DWORD read = 0;
+ BOOL rc = FALSE;
+ HANDLE file = NULL;
+ char* fdata = NULL;
+ const char* name = filemap[type];
+ char* abs = GetCombinedPath(path, name);
+
+ if (!abs)
+ return FALSE;
+
+ file = CreateFileA(abs, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ free(abs);
+
+ if (file == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ lowSize = GetFileSize(file, &highSize);
+
+ if ((lowSize == INVALID_FILE_SIZE) || (highSize != 0))
+ goto fail;
+
+ if (lowSize != 0)
+ {
+ fdata = malloc(lowSize);
+
+ if (!fdata)
+ goto fail;
+
+ rc = ReadFile(file, fdata, lowSize, &read, NULL);
+
+ if (lowSize != read)
+ rc = FALSE;
+ }
+
+fail:
+ CloseHandle(file);
+
+ if (rc && (lowSize <= INT_MAX))
+ {
+ size_t blen = 0;
+ crypto_base64_decode(fdata, lowSize, (BYTE**)data, &blen);
+
+ if (*data && (blen > 0))
+ *length = (UINT32)blen;
+ else
+ {
+ rc = FALSE;
+ *length = 0;
+ }
+ }
+ else
+ {
+ *length = 0;
+ *data = NULL;
+ }
+
+ free(fdata);
+ return rc;
+}
+
+static BOOL printer_save_to_config(const rdpSettings* settings, const char* PortDosName,
+ size_t PortDosNameLen, const WCHAR* PnPName, size_t PnPNameLen,
+ const WCHAR* DriverName, size_t DriverNameLen,
+ const WCHAR* PrinterName, size_t PrintNameLen,
+ const BYTE* CachedPrinterConfigData, size_t CacheFieldsLen)
+{
+ BOOL rc = FALSE;
+ char* path = get_printer_config_path(settings, PrinterName, PrintNameLen);
+
+ if (!path)
+ goto fail;
+
+ if (!printer_write_setting(path, PRN_CONF_PORT, PortDosName, PortDosNameLen))
+ goto fail;
+
+ if (!printer_write_setting(path, PRN_CONF_PNP, PnPName, PnPNameLen))
+ goto fail;
+
+ if (!printer_write_setting(path, PRN_CONF_DRIVER, DriverName, DriverNameLen))
+ goto fail;
+
+ if (!printer_write_setting(path, PRN_CONF_DATA, CachedPrinterConfigData, CacheFieldsLen))
+ goto fail;
+
+fail:
+ free(path);
+ return rc;
+}
+
+static BOOL printer_update_to_config(const rdpSettings* settings, const WCHAR* name, size_t length,
+ const BYTE* data, size_t datalen)
+{
+ BOOL rc = FALSE;
+ char* path = get_printer_config_path(settings, name, length);
+ rc = printer_write_setting(path, PRN_CONF_DATA, data, datalen);
+ free(path);
+ return rc;
+}
+
+static BOOL printer_remove_config(const rdpSettings* settings, const WCHAR* name, size_t length)
+{
+ BOOL rc = FALSE;
+ char* path = get_printer_config_path(settings, name, length);
+
+ if (!printer_config_valid(path))
+ goto fail;
+
+ rc = winpr_RemoveDirectory(path);
+fail:
+ free(path);
+ return rc;
+}
+
+static BOOL printer_move_config(const rdpSettings* settings, const WCHAR* oldName, size_t oldLength,
+ const WCHAR* newName, size_t newLength)
+{
+ BOOL rc = FALSE;
+ char* oldPath = get_printer_config_path(settings, oldName, oldLength);
+ char* newPath = get_printer_config_path(settings, newName, newLength);
+
+ if (printer_config_valid(oldPath))
+ rc = winpr_MoveFile(oldPath, newPath);
+
+ free(oldPath);
+ free(newPath);
+ return rc;
+}
+
+static BOOL printer_load_from_config(const rdpSettings* settings, rdpPrinter* printer,
+ PRINTER_DEVICE* printer_dev)
+{
+ BOOL res = FALSE;
+ WCHAR* wname = NULL;
+ size_t wlen = 0;
+ char* path = NULL;
+ UINT32 flags = 0;
+ void* DriverName = NULL;
+ UINT32 DriverNameLen = 0;
+ void* PnPName = NULL;
+ UINT32 PnPNameLen = 0;
+ void* CachedPrinterConfigData = NULL;
+ UINT32 CachedFieldsLen = 0;
+ UINT32 PrinterNameLen = 0;
+
+ if (!settings || !printer || !printer->name)
+ return FALSE;
+
+ wname = ConvertUtf8ToWCharAlloc(printer->name, &wlen);
+
+ if (!wname)
+ goto fail;
+
+ wlen++;
+ path = get_printer_config_path(settings, wname, wlen * sizeof(WCHAR));
+ PrinterNameLen = wlen * sizeof(WCHAR);
+
+ if (!path)
+ goto fail;
+
+ if (printer->is_default)
+ flags |= RDPDR_PRINTER_ANNOUNCE_FLAG_DEFAULTPRINTER;
+
+ if (!printer_read_setting(path, PRN_CONF_PNP, &PnPName, &PnPNameLen))
+ {
+ }
+
+ if (!printer_read_setting(path, PRN_CONF_DRIVER, &DriverName, &DriverNameLen))
+ {
+ size_t len = 0;
+ DriverName = ConvertUtf8ToWCharAlloc(printer->driver, &len);
+ if (!DriverName)
+ goto fail;
+ DriverNameLen = (len + 1) * sizeof(WCHAR);
+ }
+
+ if (!printer_read_setting(path, PRN_CONF_DATA, &CachedPrinterConfigData, &CachedFieldsLen))
+ {
+ }
+
+ Stream_SetPosition(printer_dev->device.data, 0);
+
+ if (!Stream_EnsureRemainingCapacity(printer_dev->device.data, 24))
+ goto fail;
+
+ Stream_Write_UINT32(printer_dev->device.data, flags);
+ Stream_Write_UINT32(printer_dev->device.data, 0); /* CodePage, reserved */
+ Stream_Write_UINT32(printer_dev->device.data, PnPNameLen); /* PnPNameLen */
+ Stream_Write_UINT32(printer_dev->device.data, DriverNameLen);
+ Stream_Write_UINT32(printer_dev->device.data, PrinterNameLen);
+ Stream_Write_UINT32(printer_dev->device.data, CachedFieldsLen);
+
+ if (!Stream_EnsureRemainingCapacity(printer_dev->device.data, PnPNameLen))
+ goto fail;
+
+ if (PnPNameLen > 0)
+ Stream_Write(printer_dev->device.data, PnPName, PnPNameLen);
+
+ if (!Stream_EnsureRemainingCapacity(printer_dev->device.data, DriverNameLen))
+ goto fail;
+
+ Stream_Write(printer_dev->device.data, DriverName, DriverNameLen);
+
+ if (!Stream_EnsureRemainingCapacity(printer_dev->device.data, PrinterNameLen))
+ goto fail;
+
+ union
+ {
+ char c[2];
+ WCHAR w;
+ } backslash;
+ backslash.c[0] = '\\';
+ backslash.c[1] = '\0';
+
+ for (WCHAR* wptr = wname; (wptr = _wcschr(wptr, backslash.w));)
+ *wptr = L'_';
+ Stream_Write(printer_dev->device.data, wname, PrinterNameLen);
+
+ if (!Stream_EnsureRemainingCapacity(printer_dev->device.data, CachedFieldsLen))
+ goto fail;
+
+ Stream_Write(printer_dev->device.data, CachedPrinterConfigData, CachedFieldsLen);
+ res = TRUE;
+fail:
+ free(path);
+ free(wname);
+ free(PnPName);
+ free(DriverName);
+ free(CachedPrinterConfigData);
+ return res;
+}
+
+static BOOL printer_save_default_config(const rdpSettings* settings, rdpPrinter* printer)
+{
+ BOOL res = FALSE;
+ WCHAR* wname = NULL;
+ WCHAR* driver = NULL;
+ size_t wlen = 0;
+ size_t dlen = 0;
+ char* path = NULL;
+
+ if (!settings || !printer || !printer->name || !printer->driver)
+ return FALSE;
+
+ wname = ConvertUtf8ToWCharAlloc(printer->name, NULL);
+
+ if (!wname)
+ goto fail;
+
+ driver = ConvertUtf8ToWCharAlloc(printer->driver, NULL);
+
+ if (!driver)
+ goto fail;
+
+ wlen = _wcslen(wname) + 1;
+ dlen = _wcslen(driver) + 1;
+ path = get_printer_config_path(settings, wname, wlen * sizeof(WCHAR));
+
+ if (!path)
+ goto fail;
+
+ if (dlen > 1)
+ {
+ if (!printer_write_setting(path, PRN_CONF_DRIVER, driver, dlen * sizeof(WCHAR)))
+ goto fail;
+ }
+
+ res = TRUE;
+fail:
+ free(path);
+ free(wname);
+ free(driver);
+ return res;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT printer_process_irp_create(PRINTER_DEVICE* printer_dev, IRP* irp)
+{
+ rdpPrintJob* printjob = NULL;
+
+ WINPR_ASSERT(printer_dev);
+ WINPR_ASSERT(irp);
+
+ if (printer_dev->printer)
+ {
+ WINPR_ASSERT(printer_dev->printer->CreatePrintJob);
+ printjob =
+ printer_dev->printer->CreatePrintJob(printer_dev->printer, irp->devman->id_sequence++);
+ }
+
+ if (printjob)
+ {
+ Stream_Write_UINT32(irp->output, printjob->id); /* FileId */
+ }
+ else
+ {
+ Stream_Write_UINT32(irp->output, 0); /* FileId */
+ irp->IoStatus = STATUS_PRINT_QUEUE_FULL;
+ }
+
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT printer_process_irp_close(PRINTER_DEVICE* printer_dev, IRP* irp)
+{
+ rdpPrintJob* printjob = NULL;
+
+ WINPR_ASSERT(printer_dev);
+ WINPR_ASSERT(irp);
+
+ if (printer_dev->printer)
+ {
+ WINPR_ASSERT(printer_dev->printer->FindPrintJob);
+ printjob = printer_dev->printer->FindPrintJob(printer_dev->printer, irp->FileId);
+ }
+
+ if (!printjob)
+ {
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ }
+ else
+ {
+ printjob->Close(printjob);
+ }
+
+ Stream_Zero(irp->output, 4); /* Padding(4) */
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT printer_process_irp_write(PRINTER_DEVICE* printer_dev, IRP* irp)
+{
+ UINT32 Length = 0;
+ UINT64 Offset = 0;
+ rdpPrintJob* printjob = NULL;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(printer_dev);
+ WINPR_ASSERT(irp);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32))
+ return ERROR_INVALID_DATA;
+ Stream_Read_UINT32(irp->input, Length);
+ Stream_Read_UINT64(irp->input, Offset);
+ Stream_Seek(irp->input, 20); /* Padding */
+ const void* ptr = Stream_ConstPointer(irp->input);
+ if (!Stream_SafeSeek(irp->input, Length))
+ return ERROR_INVALID_DATA;
+ if (printer_dev->printer)
+ {
+ WINPR_ASSERT(printer_dev->printer->FindPrintJob);
+ printjob = printer_dev->printer->FindPrintJob(printer_dev->printer, irp->FileId);
+ }
+
+ if (!printjob)
+ {
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ Length = 0;
+ }
+ else
+ {
+ error = printjob->Write(printjob, ptr, Length);
+ }
+
+ if (error)
+ {
+ WLog_ERR(TAG, "printjob->Write failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ Stream_Write_UINT32(irp->output, Length);
+ Stream_Write_UINT8(irp->output, 0); /* Padding */
+
+ WINPR_ASSERT(irp->Complete);
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT printer_process_irp_device_control(PRINTER_DEVICE* printer_dev, IRP* irp)
+{
+ WINPR_ASSERT(printer_dev);
+ WINPR_ASSERT(irp);
+
+ Stream_Write_UINT32(irp->output, 0); /* OutputBufferLength */
+
+ WINPR_ASSERT(irp->Complete);
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT printer_process_irp(PRINTER_DEVICE* printer_dev, IRP* irp)
+{
+ UINT error = 0;
+
+ WINPR_ASSERT(printer_dev);
+ WINPR_ASSERT(irp);
+
+ switch (irp->MajorFunction)
+ {
+ case IRP_MJ_CREATE:
+ if ((error = printer_process_irp_create(printer_dev, irp)))
+ {
+ WLog_ERR(TAG, "printer_process_irp_create failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ break;
+
+ case IRP_MJ_CLOSE:
+ if ((error = printer_process_irp_close(printer_dev, irp)))
+ {
+ WLog_ERR(TAG, "printer_process_irp_close failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ break;
+
+ case IRP_MJ_WRITE:
+ if ((error = printer_process_irp_write(printer_dev, irp)))
+ {
+ WLog_ERR(TAG, "printer_process_irp_write failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ break;
+
+ case IRP_MJ_DEVICE_CONTROL:
+ if ((error = printer_process_irp_device_control(printer_dev, irp)))
+ {
+ WLog_ERR(TAG, "printer_process_irp_device_control failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ default:
+ irp->IoStatus = STATUS_NOT_SUPPORTED;
+ WINPR_ASSERT(irp->Complete);
+ return irp->Complete(irp);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static DWORD WINAPI printer_thread_func(LPVOID arg)
+{
+ IRP* irp = NULL;
+ PRINTER_DEVICE* printer_dev = (PRINTER_DEVICE*)arg;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(printer_dev);
+
+ while (1)
+ {
+ HANDLE obj[] = { printer_dev->event, printer_dev->stopEvent };
+ DWORD rc = WaitForMultipleObjects(ARRAYSIZE(obj), obj, FALSE, INFINITE);
+
+ if (rc == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error);
+ break;
+ }
+
+ if (rc == WAIT_OBJECT_0 + 1)
+ break;
+ else if (rc != WAIT_OBJECT_0)
+ continue;
+
+ ResetEvent(printer_dev->event);
+ irp = (IRP*)InterlockedPopEntrySList(printer_dev->pIrpList);
+
+ if (irp == NULL)
+ {
+ WLog_ERR(TAG, "InterlockedPopEntrySList failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if ((error = printer_process_irp(printer_dev, irp)))
+ {
+ WLog_ERR(TAG, "printer_process_irp failed with error %" PRIu32 "!", error);
+ break;
+ }
+ }
+
+ if (error && printer_dev->rdpcontext)
+ setChannelError(printer_dev->rdpcontext, error, "printer_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT printer_irp_request(DEVICE* device, IRP* irp)
+{
+ PRINTER_DEVICE* printer_dev = (PRINTER_DEVICE*)device;
+
+ WINPR_ASSERT(printer_dev);
+ WINPR_ASSERT(irp);
+
+ InterlockedPushEntrySList(printer_dev->pIrpList, &(irp->ItemEntry));
+ SetEvent(printer_dev->event);
+ return CHANNEL_RC_OK;
+}
+
+static UINT printer_custom_component(DEVICE* device, UINT16 component, UINT16 packetId, wStream* s)
+{
+ UINT32 eventID = 0;
+ PRINTER_DEVICE* printer_dev = (PRINTER_DEVICE*)device;
+
+ WINPR_ASSERT(printer_dev);
+ WINPR_ASSERT(printer_dev->rdpcontext);
+
+ const rdpSettings* settings = printer_dev->rdpcontext->settings;
+ WINPR_ASSERT(settings);
+
+ if (component != RDPDR_CTYP_PRN)
+ return ERROR_INVALID_DATA;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, eventID);
+
+ switch (packetId)
+ {
+ case PAKID_PRN_CACHE_DATA:
+ switch (eventID)
+ {
+ case RDPDR_ADD_PRINTER_EVENT:
+ {
+ char PortDosName[8];
+ UINT32 PnPNameLen = 0;
+ UINT32 DriverNameLen = 0;
+ UINT32 PrintNameLen = 0;
+ UINT32 CacheFieldsLen = 0;
+ const WCHAR* PnPName = NULL;
+ const WCHAR* DriverName = NULL;
+ const WCHAR* PrinterName = NULL;
+ const BYTE* CachedPrinterConfigData = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 24))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read(s, PortDosName, sizeof(PortDosName));
+ Stream_Read_UINT32(s, PnPNameLen);
+ Stream_Read_UINT32(s, DriverNameLen);
+ Stream_Read_UINT32(s, PrintNameLen);
+ Stream_Read_UINT32(s, CacheFieldsLen);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, PnPNameLen))
+ return ERROR_INVALID_DATA;
+
+ PnPName = Stream_ConstPointer(s);
+ Stream_Seek(s, PnPNameLen);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, DriverNameLen))
+ return ERROR_INVALID_DATA;
+
+ DriverName = Stream_ConstPointer(s);
+ Stream_Seek(s, DriverNameLen);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, PrintNameLen))
+ return ERROR_INVALID_DATA;
+
+ PrinterName = Stream_ConstPointer(s);
+ Stream_Seek(s, PrintNameLen);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, CacheFieldsLen))
+ return ERROR_INVALID_DATA;
+
+ CachedPrinterConfigData = Stream_ConstPointer(s);
+ Stream_Seek(s, CacheFieldsLen);
+
+ if (!printer_save_to_config(settings, PortDosName, sizeof(PortDosName), PnPName,
+ PnPNameLen, DriverName, DriverNameLen, PrinterName,
+ PrintNameLen, CachedPrinterConfigData,
+ CacheFieldsLen))
+ return ERROR_INTERNAL_ERROR;
+ }
+ break;
+
+ case RDPDR_UPDATE_PRINTER_EVENT:
+ {
+ UINT32 PrinterNameLen = 0;
+ UINT32 ConfigDataLen = 0;
+ const WCHAR* PrinterName = NULL;
+ const BYTE* ConfigData = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, PrinterNameLen);
+ Stream_Read_UINT32(s, ConfigDataLen);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, PrinterNameLen))
+ return ERROR_INVALID_DATA;
+
+ PrinterName = Stream_ConstPointer(s);
+ Stream_Seek(s, PrinterNameLen);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, ConfigDataLen))
+ return ERROR_INVALID_DATA;
+
+ ConfigData = Stream_ConstPointer(s);
+ Stream_Seek(s, ConfigDataLen);
+
+ if (!printer_update_to_config(settings, PrinterName, PrinterNameLen, ConfigData,
+ ConfigDataLen))
+ return ERROR_INTERNAL_ERROR;
+ }
+ break;
+
+ case RDPDR_DELETE_PRINTER_EVENT:
+ {
+ UINT32 PrinterNameLen = 0;
+ const WCHAR* PrinterName = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, PrinterNameLen);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, PrinterNameLen))
+ return ERROR_INVALID_DATA;
+
+ PrinterName = Stream_ConstPointer(s);
+ Stream_Seek(s, PrinterNameLen);
+ printer_remove_config(settings, PrinterName, PrinterNameLen);
+ }
+ break;
+
+ case RDPDR_RENAME_PRINTER_EVENT:
+ {
+ UINT32 OldPrinterNameLen = 0;
+ UINT32 NewPrinterNameLen = 0;
+ const WCHAR* OldPrinterName = NULL;
+ const WCHAR* NewPrinterName = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, OldPrinterNameLen);
+ Stream_Read_UINT32(s, NewPrinterNameLen);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, OldPrinterNameLen))
+ return ERROR_INVALID_DATA;
+
+ OldPrinterName = Stream_ConstPointer(s);
+ Stream_Seek(s, OldPrinterNameLen);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, NewPrinterNameLen))
+ return ERROR_INVALID_DATA;
+
+ NewPrinterName = Stream_ConstPointer(s);
+ Stream_Seek(s, NewPrinterNameLen);
+
+ if (!printer_move_config(settings, OldPrinterName, OldPrinterNameLen,
+ NewPrinterName, NewPrinterNameLen))
+ return ERROR_INTERNAL_ERROR;
+ }
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unknown cache data eventID: 0x%08" PRIX32 "", eventID);
+ return ERROR_INVALID_DATA;
+ }
+
+ break;
+
+ case PAKID_PRN_USING_XPS:
+ {
+ UINT32 flags = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, flags);
+ WLog_ERR(TAG,
+ "Ignoring unhandled message PAKID_PRN_USING_XPS [printerID=%08" PRIx32
+ ", flags=%08" PRIx32 "]",
+ eventID, flags);
+ }
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unknown printing component packetID: 0x%04" PRIX16 "", packetId);
+ return ERROR_INVALID_DATA;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT printer_free(DEVICE* device)
+{
+ IRP* irp = NULL;
+ PRINTER_DEVICE* printer_dev = (PRINTER_DEVICE*)device;
+ UINT error = 0;
+
+ WINPR_ASSERT(printer_dev);
+
+ SetEvent(printer_dev->stopEvent);
+
+ if (WaitForSingleObject(printer_dev->thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+
+ /* The analyzer is confused by this premature return value.
+ * Since this case can not be handled gracefully silence the
+ * analyzer here. */
+#ifndef __clang_analyzer__
+ return error;
+#endif
+ }
+
+ while ((irp = (IRP*)InterlockedPopEntrySList(printer_dev->pIrpList)) != NULL)
+ {
+ WINPR_ASSERT(irp->Discard);
+ irp->Discard(irp);
+ }
+
+ CloseHandle(printer_dev->thread);
+ CloseHandle(printer_dev->stopEvent);
+ CloseHandle(printer_dev->event);
+ winpr_aligned_free(printer_dev->pIrpList);
+
+ if (printer_dev->printer)
+ {
+ WINPR_ASSERT(printer_dev->printer->ReleaseRef);
+ printer_dev->printer->ReleaseRef(printer_dev->printer);
+ }
+
+ Stream_Free(printer_dev->device.data, TRUE);
+ free(printer_dev);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT printer_register(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints, rdpPrinter* printer)
+{
+ PRINTER_DEVICE* printer_dev = NULL;
+ UINT error = ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(pEntryPoints);
+ WINPR_ASSERT(printer);
+
+ printer_dev = (PRINTER_DEVICE*)calloc(1, sizeof(PRINTER_DEVICE));
+
+ if (!printer_dev)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ printer_dev->device.data = Stream_New(NULL, 1024);
+
+ if (!printer_dev->device.data)
+ goto error_out;
+
+ sprintf_s(printer_dev->port, sizeof(printer_dev->port), "PRN%" PRIdz, printer->id);
+ printer_dev->device.type = RDPDR_DTYP_PRINT;
+ printer_dev->device.name = printer_dev->port;
+ printer_dev->device.IRPRequest = printer_irp_request;
+ printer_dev->device.CustomComponentRequest = printer_custom_component;
+ printer_dev->device.Free = printer_free;
+ printer_dev->rdpcontext = pEntryPoints->rdpcontext;
+ printer_dev->printer = printer;
+ printer_dev->pIrpList = (WINPR_PSLIST_HEADER)winpr_aligned_malloc(sizeof(WINPR_SLIST_HEADER),
+ MEMORY_ALLOCATION_ALIGNMENT);
+
+ if (!printer_dev->pIrpList)
+ {
+ WLog_ERR(TAG, "_aligned_malloc failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+
+ if (!printer_load_from_config(pEntryPoints->rdpcontext->settings, printer, printer_dev))
+ goto error_out;
+
+ InitializeSListHead(printer_dev->pIrpList);
+
+ if (!(printer_dev->event = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto error_out;
+ }
+
+ if (!(printer_dev->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto error_out;
+ }
+
+ if ((error = pEntryPoints->RegisterDevice(pEntryPoints->devman, &printer_dev->device)))
+ {
+ WLog_ERR(TAG, "RegisterDevice failed with error %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ if (!(printer_dev->thread =
+ CreateThread(NULL, 0, printer_thread_func, (void*)printer_dev, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto error_out;
+ }
+
+ WINPR_ASSERT(printer->AddRef);
+ printer->AddRef(printer);
+ return CHANNEL_RC_OK;
+error_out:
+ printer_free(&printer_dev->device);
+ return error;
+}
+
+static rdpPrinterDriver* printer_load_backend(const char* backend)
+{
+ typedef UINT (*backend_load_t)(rdpPrinterDriver**);
+ union
+ {
+ PVIRTUALCHANNELENTRY entry;
+ backend_load_t backend;
+ } fktconv;
+
+ fktconv.entry = freerdp_load_channel_addin_entry("printer", backend, NULL, 0);
+ if (!fktconv.entry)
+ return NULL;
+
+ rdpPrinterDriver* printer = NULL;
+ const UINT rc = fktconv.backend(&printer);
+ if (rc != CHANNEL_RC_OK)
+ return NULL;
+
+ return printer;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT printer_DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints))
+{
+ char* name = NULL;
+ char* driver_name = NULL;
+ BOOL default_backend = TRUE;
+ RDPDR_PRINTER* device = NULL;
+ rdpPrinterDriver* driver = NULL;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!pEntryPoints || !pEntryPoints->device)
+ return ERROR_INVALID_PARAMETER;
+
+ device = (RDPDR_PRINTER*)pEntryPoints->device;
+ name = device->device.Name;
+ driver_name = _strdup(device->DriverName);
+
+ /* Secondary argument is one of the following:
+ *
+ * <driver_name> ... name of a printer driver
+ * <driver_name>:<backend_name> ... name of a printer driver and local printer backend to use
+ */
+ if (driver_name)
+ {
+ char* sep = strstr(driver_name, ":");
+ if (sep)
+ {
+ const char* backend = sep + 1;
+ *sep = '\0';
+ driver = printer_load_backend(backend);
+ default_backend = FALSE;
+ }
+ }
+
+ if (!driver && default_backend)
+ {
+ const char* backend =
+#if defined(WITH_CUPS)
+ "cups"
+#elif defined(_WIN32)
+ "win"
+#else
+ ""
+#endif
+ ;
+
+ driver = printer_load_backend(backend);
+ }
+
+ if (!driver)
+ {
+ WLog_ERR(TAG, "Could not get a printer driver!");
+ error = CHANNEL_RC_INITIALIZATION_ERROR;
+ goto fail;
+ }
+
+ if (name && name[0])
+ {
+ WINPR_ASSERT(driver->GetPrinter);
+ rdpPrinter* printer = driver->GetPrinter(driver, name, driver_name, device->IsDefault);
+
+ if (!printer)
+ {
+ WLog_ERR(TAG, "Could not get printer %s!", name);
+ error = CHANNEL_RC_INITIALIZATION_ERROR;
+ goto fail;
+ }
+
+ WINPR_ASSERT(printer->ReleaseRef);
+ if (!printer_save_default_config(pEntryPoints->rdpcontext->settings, printer))
+ {
+ error = CHANNEL_RC_INITIALIZATION_ERROR;
+ printer->ReleaseRef(printer);
+ goto fail;
+ }
+
+ error = printer_register(pEntryPoints, printer);
+ printer->ReleaseRef(printer);
+ if (error)
+ {
+ WLog_ERR(TAG, "printer_register failed with error %" PRIu32 "!", error);
+ goto fail;
+ }
+ }
+ else
+ {
+ WINPR_ASSERT(driver->EnumPrinters);
+ rdpPrinter** printers = driver->EnumPrinters(driver);
+ if (printers)
+ {
+ for (rdpPrinter** current = printers; *current; ++current)
+ {
+ error = printer_register(pEntryPoints, *current);
+ if (error)
+ {
+ WLog_ERR(TAG, "printer_register failed with error %" PRIu32 "!", error);
+ break;
+ }
+ }
+ }
+ else
+ {
+ WLog_ERR(TAG, "Failed to enumerate printers!");
+ error = CHANNEL_RC_INITIALIZATION_ERROR;
+ }
+
+ WINPR_ASSERT(driver->ReleaseEnumPrinters);
+ driver->ReleaseEnumPrinters(printers);
+ }
+
+fail:
+ free(driver_name);
+ if (driver)
+ {
+ WINPR_ASSERT(driver->ReleaseRef);
+ driver->ReleaseRef(driver);
+ }
+
+ return error;
+}
diff --git a/channels/printer/client/win/CMakeLists.txt b/channels/printer/client/win/CMakeLists.txt
new file mode 100644
index 0000000..7f0960a
--- /dev/null
+++ b/channels/printer/client/win/CMakeLists.txt
@@ -0,0 +1,30 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2019 Armin Novak <armin.novak@thincast.com>
+# Copyright 2019 Thincast Technologies GmbH
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+define_channel_client_subsystem("printer" "win" "")
+
+set(${MODULE_PREFIX}_SRCS
+ printer_win.c)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+)
+
+include_directories(..)
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/printer/client/win/printer_win.c b/channels/printer/client/win/printer_win.c
new file mode 100644
index 0000000..9bd7589
--- /dev/null
+++ b/channels/printer/client/win/printer_win.c
@@ -0,0 +1,463 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Print Virtual Channel - WIN driver
+ *
+ * Copyright 2012 Gerald Richter
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 Armin Novak <armin.novak@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/string.h>
+#include <winpr/windows.h>
+
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <winspool.h>
+
+#include <freerdp/client/printer.h>
+
+#define WIDEN_INT(x) L##x
+#define WIDEN(x) WIDEN_INT(x)
+#define PRINTER_TAG CHANNELS_TAG("printer.client")
+#ifdef WITH_DEBUG_WINPR
+#define DEBUG_WINPR(...) WLog_DBG(PRINTER_TAG, __VA_ARGS__)
+#else
+#define DEBUG_WINPR(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+typedef struct
+{
+ rdpPrinterDriver driver;
+
+ size_t id_sequence;
+ size_t references;
+} rdpWinPrinterDriver;
+
+typedef struct
+{
+ rdpPrintJob printjob;
+ DOC_INFO_1 di;
+ DWORD handle;
+
+ void* printjob_object;
+ int printjob_id;
+} rdpWinPrintJob;
+
+typedef struct
+{
+ rdpPrinter printer;
+ HANDLE hPrinter;
+ rdpWinPrintJob* printjob;
+} rdpWinPrinter;
+
+static WCHAR* printer_win_get_printjob_name(size_t id)
+{
+ time_t tt;
+ struct tm tres;
+ errno_t err;
+ WCHAR* str;
+ size_t len = 1024;
+ int rc;
+
+ tt = time(NULL);
+ err = localtime_s(&tres, &tt);
+
+ str = calloc(len, sizeof(WCHAR));
+ if (!str)
+ return NULL;
+
+ rc = swprintf_s(str, len,
+ WIDEN("FreeRDP Print %04d-%02d-%02d% 02d-%02d-%02d - Job %") WIDEN(PRIuz)
+ WIDEN("\0"),
+ tres.tm_year + 1900, tres.tm_mon + 1, tres.tm_mday, tres.tm_hour, tres.tm_min,
+ tres.tm_sec, id);
+
+ return str;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT printer_win_write_printjob(rdpPrintJob* printjob, const BYTE* data, size_t size)
+{
+ rdpWinPrinter* printer;
+ LPCVOID pBuf = data;
+ DWORD cbBuf = size;
+ DWORD pcWritten;
+
+ if (!printjob || !data)
+ return ERROR_BAD_ARGUMENTS;
+
+ printer = (rdpWinPrinter*)printjob->printer;
+ if (!printer)
+ return ERROR_BAD_ARGUMENTS;
+
+ if (!WritePrinter(printer->hPrinter, pBuf, cbBuf, &pcWritten))
+ return ERROR_INTERNAL_ERROR;
+ return CHANNEL_RC_OK;
+}
+
+static void printer_win_close_printjob(rdpPrintJob* printjob)
+{
+ rdpWinPrintJob* win_printjob = (rdpWinPrintJob*)printjob;
+ rdpWinPrinter* win_printer;
+
+ if (!printjob)
+ return;
+
+ win_printer = (rdpWinPrinter*)printjob->printer;
+ if (!win_printer)
+ return;
+
+ if (!EndPagePrinter(win_printer->hPrinter))
+ {
+ }
+
+ if (!ClosePrinter(win_printer->hPrinter))
+ {
+ }
+
+ win_printer->printjob = NULL;
+
+ free(win_printjob->di.pDocName);
+ free(win_printjob);
+}
+
+static rdpPrintJob* printer_win_create_printjob(rdpPrinter* printer, UINT32 id)
+{
+ rdpWinPrinter* win_printer = (rdpWinPrinter*)printer;
+ rdpWinPrintJob* win_printjob;
+
+ if (win_printer->printjob != NULL)
+ return NULL;
+
+ win_printjob = (rdpWinPrintJob*)calloc(1, sizeof(rdpWinPrintJob));
+ if (!win_printjob)
+ return NULL;
+
+ win_printjob->printjob.id = id;
+ win_printjob->printjob.printer = printer;
+ win_printjob->di.pDocName = printer_win_get_printjob_name(id);
+ win_printjob->di.pDatatype = NULL;
+ win_printjob->di.pOutputFile = NULL;
+
+ win_printjob->handle = StartDocPrinter(win_printer->hPrinter, 1, (LPBYTE) & (win_printjob->di));
+
+ if (!win_printjob->handle)
+ {
+ free(win_printjob->di.pDocName);
+ free(win_printjob);
+ return NULL;
+ }
+
+ if (!StartPagePrinter(win_printer->hPrinter))
+ {
+ free(win_printjob->di.pDocName);
+ free(win_printjob);
+ return NULL;
+ }
+
+ win_printjob->printjob.Write = printer_win_write_printjob;
+ win_printjob->printjob.Close = printer_win_close_printjob;
+
+ win_printer->printjob = win_printjob;
+
+ return &win_printjob->printjob;
+}
+
+static rdpPrintJob* printer_win_find_printjob(rdpPrinter* printer, UINT32 id)
+{
+ rdpWinPrinter* win_printer = (rdpWinPrinter*)printer;
+
+ if (!win_printer->printjob)
+ return NULL;
+
+ if (win_printer->printjob->printjob.id != id)
+ return NULL;
+
+ return (rdpPrintJob*)win_printer->printjob;
+}
+
+static void printer_win_free_printer(rdpPrinter* printer)
+{
+ rdpWinPrinter* win_printer = (rdpWinPrinter*)printer;
+
+ if (win_printer->printjob)
+ win_printer->printjob->printjob.Close((rdpPrintJob*)win_printer->printjob);
+
+ if (printer->backend)
+ printer->backend->ReleaseRef(printer->backend);
+
+ free(printer->name);
+ free(printer->driver);
+ free(printer);
+}
+
+static void printer_win_add_ref_printer(rdpPrinter* printer)
+{
+ if (printer)
+ printer->references++;
+}
+
+static void printer_win_release_ref_printer(rdpPrinter* printer)
+{
+ if (!printer)
+ return;
+ if (printer->references <= 1)
+ printer_win_free_printer(printer);
+ else
+ printer->references--;
+}
+
+static rdpPrinter* printer_win_new_printer(rdpWinPrinterDriver* win_driver, const WCHAR* name,
+ const WCHAR* drivername, BOOL is_default)
+{
+ rdpWinPrinter* win_printer;
+ DWORD needed = 0;
+ PRINTER_INFO_2* prninfo = NULL;
+
+ if (!name)
+ return NULL;
+
+ win_printer = (rdpWinPrinter*)calloc(1, sizeof(rdpWinPrinter));
+ if (!win_printer)
+ return NULL;
+
+ win_printer->printer.backend = &win_driver->driver;
+ win_printer->printer.id = win_driver->id_sequence++;
+ win_printer->printer.name = ConvertWCharToUtf8Alloc(name, NULL);
+ if (!win_printer->printer.name)
+ goto fail;
+
+ if (!win_printer->printer.name)
+ goto fail;
+ win_printer->printer.is_default = is_default;
+
+ win_printer->printer.CreatePrintJob = printer_win_create_printjob;
+ win_printer->printer.FindPrintJob = printer_win_find_printjob;
+ win_printer->printer.AddRef = printer_win_add_ref_printer;
+ win_printer->printer.ReleaseRef = printer_win_release_ref_printer;
+
+ if (!OpenPrinter(name, &(win_printer->hPrinter), NULL))
+ goto fail;
+
+ /* How many memory should be allocated for printer data */
+ GetPrinter(win_printer->hPrinter, 2, (LPBYTE)prninfo, 0, &needed);
+ if (needed == 0)
+ goto fail;
+
+ prninfo = (PRINTER_INFO_2*)GlobalAlloc(GPTR, needed);
+ if (!prninfo)
+ goto fail;
+
+ if (!GetPrinter(win_printer->hPrinter, 2, (LPBYTE)prninfo, needed, &needed))
+ {
+ GlobalFree(prninfo);
+ goto fail;
+ }
+
+ if (drivername)
+ win_printer->printer.driver = ConvertWCharToUtf8Alloc(drivername, NULL);
+ else
+ win_printer->printer.driver = ConvertWCharToUtf8Alloc(prninfo->pDriverName, NULL);
+ GlobalFree(prninfo);
+ if (!win_printer->printer.driver)
+ goto fail;
+
+ win_printer->printer.AddRef(&win_printer->printer);
+ win_printer->printer.backend->AddRef(win_printer->printer.backend);
+ return &win_printer->printer;
+
+fail:
+ printer_win_free_printer(&win_printer->printer);
+ return NULL;
+}
+
+static void printer_win_release_enum_printers(rdpPrinter** printers)
+{
+ rdpPrinter** cur = printers;
+
+ while ((cur != NULL) && ((*cur) != NULL))
+ {
+ if ((*cur)->ReleaseRef)
+ (*cur)->ReleaseRef(*cur);
+ cur++;
+ }
+ free(printers);
+}
+
+static rdpPrinter** printer_win_enum_printers(rdpPrinterDriver* driver)
+{
+ rdpPrinter** printers;
+ int num_printers;
+ PRINTER_INFO_2* prninfo = NULL;
+ DWORD needed, returned;
+ BOOL haveDefault = FALSE;
+ LPWSTR defaultPrinter = NULL;
+
+ GetDefaultPrinter(NULL, &needed);
+ if (needed)
+ {
+ defaultPrinter = (LPWSTR)calloc(needed, sizeof(WCHAR));
+
+ if (!defaultPrinter)
+ return NULL;
+
+ if (!GetDefaultPrinter(defaultPrinter, &needed))
+ defaultPrinter[0] = '\0';
+ }
+
+ /* find required size for the buffer */
+ EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, NULL, 2, NULL, 0, &needed,
+ &returned);
+
+ /* allocate array of PRINTER_INFO structures */
+ prninfo = (PRINTER_INFO_2*)GlobalAlloc(GPTR, needed);
+ if (!prninfo)
+ {
+ free(defaultPrinter);
+ return NULL;
+ }
+
+ /* call again */
+ if (!EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, NULL, 2, (LPBYTE)prninfo,
+ needed, &needed, &returned))
+ {
+ }
+
+ printers = (rdpPrinter**)calloc((returned + 1), sizeof(rdpPrinter*));
+ if (!printers)
+ {
+ GlobalFree(prninfo);
+ free(defaultPrinter);
+ return NULL;
+ }
+
+ num_printers = 0;
+
+ for (int i = 0; i < (int)returned; i++)
+ {
+ rdpPrinter* current = printers[num_printers];
+ current = printer_win_new_printer((rdpWinPrinterDriver*)driver, prninfo[i].pPrinterName,
+ prninfo[i].pDriverName,
+ _wcscmp(prninfo[i].pPrinterName, defaultPrinter) == 0);
+ if (!current)
+ {
+ printer_win_release_enum_printers(printers);
+ printers = NULL;
+ break;
+ }
+ if (current->is_default)
+ haveDefault = TRUE;
+ printers[num_printers++] = current;
+ }
+
+ if (!haveDefault && (returned > 0))
+ printers[0]->is_default = TRUE;
+
+ GlobalFree(prninfo);
+ free(defaultPrinter);
+ return printers;
+}
+
+static rdpPrinter* printer_win_get_printer(rdpPrinterDriver* driver, const char* name,
+ const char* driverName, BOOL isDefault)
+{
+ WCHAR* driverNameW = NULL;
+ WCHAR* nameW = NULL;
+ rdpWinPrinterDriver* win_driver = (rdpWinPrinterDriver*)driver;
+ rdpPrinter* myPrinter = NULL;
+
+ if (name)
+ {
+ nameW = ConvertUtf8ToWCharAlloc(name, NULL);
+ if (!nameW)
+ return NULL;
+ }
+ if (driverName)
+ {
+ driverNameW = ConvertUtf8ToWCharAlloc(driverName, NULL);
+ if (!driverNameW)
+ return NULL;
+ }
+
+ myPrinter = printer_win_new_printer(win_driver, nameW, driverNameW, isDefault);
+ free(driverNameW);
+ free(nameW);
+
+ return myPrinter;
+}
+
+static void printer_win_add_ref_driver(rdpPrinterDriver* driver)
+{
+ rdpWinPrinterDriver* win = (rdpWinPrinterDriver*)driver;
+ if (win)
+ win->references++;
+}
+
+/* Singleton */
+static rdpWinPrinterDriver* win_driver = NULL;
+
+static void printer_win_release_ref_driver(rdpPrinterDriver* driver)
+{
+ rdpWinPrinterDriver* win = (rdpWinPrinterDriver*)driver;
+ if (win->references <= 1)
+ {
+ free(win);
+ win_driver = NULL;
+ }
+ else
+ win->references--;
+}
+
+FREERDP_ENTRY_POINT(UINT win_freerdp_printer_client_subsystem_entry(void* arg))
+{
+ rdpPrinterDriver** ppPrinter = (rdpPrinterDriver**)arg;
+ if (!ppPrinter)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!win_driver)
+ {
+ win_driver = (rdpWinPrinterDriver*)calloc(1, sizeof(rdpWinPrinterDriver));
+
+ if (!win_driver)
+ return ERROR_OUTOFMEMORY;
+
+ win_driver->driver.EnumPrinters = printer_win_enum_printers;
+ win_driver->driver.ReleaseEnumPrinters = printer_win_release_enum_printers;
+ win_driver->driver.GetPrinter = printer_win_get_printer;
+
+ win_driver->driver.AddRef = printer_win_add_ref_driver;
+ win_driver->driver.ReleaseRef = printer_win_release_ref_driver;
+
+ win_driver->id_sequence = 1;
+ }
+
+ win_driver->driver.AddRef(&win_driver->driver);
+
+ *ppPrinter = &win_driver->driver;
+ return CHANNEL_RC_OK;
+}
diff --git a/channels/printer/printer.h b/channels/printer/printer.h
new file mode 100644
index 0000000..ae0902d
--- /dev/null
+++ b/channels/printer/printer.h
@@ -0,0 +1,36 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Definition for the printer channel
+ *
+ * Copyright 2016 David Fort <contact@hardening-consulting.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_PRINTER_PRINTER_H
+#define FREERDP_CHANNEL_PRINTER_PRINTER_H
+
+/* SERVER_PRINTER_CACHE_EVENT.cachedata */
+#define RDPDR_ADD_PRINTER_EVENT 0x00000001
+#define RDPDR_UPDATE_PRINTER_EVENT 0x00000002
+#define RDPDR_DELETE_PRINTER_EVENT 0x00000003
+#define RDPDR_RENAME_PRINTER_EVENT 0x00000004
+
+/* DR_PRN_DEVICE_ANNOUNCE.Flags */
+#define RDPDR_PRINTER_ANNOUNCE_FLAG_ASCII 0x00000001
+#define RDPDR_PRINTER_ANNOUNCE_FLAG_DEFAULTPRINTER 0x00000002
+#define RDPDR_PRINTER_ANNOUNCE_FLAG_NETWORKPRINTER 0x00000004
+#define RDPDR_PRINTER_ANNOUNCE_FLAG_TSPRINTER 0x00000008
+#define RDPDR_PRINTER_ANNOUNCE_FLAG_XPSFORMAT 0x00000010
+
+#endif /* FREERDP_CHANNEL_PRINTER_PRINTER_H */
diff --git a/channels/rail/CMakeLists.txt b/channels/rail/CMakeLists.txt
new file mode 100644
index 0000000..d372dda
--- /dev/null
+++ b/channels/rail/CMakeLists.txt
@@ -0,0 +1,26 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("rail")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/rail/ChannelOptions.cmake b/channels/rail/ChannelOptions.cmake
new file mode 100644
index 0000000..76f8571
--- /dev/null
+++ b/channels/rail/ChannelOptions.cmake
@@ -0,0 +1,13 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT OFF)
+
+define_channel_options(NAME "rail" TYPE "static"
+ DESCRIPTION "Remote Programs Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPERP]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/rail/client/CMakeLists.txt b/channels/rail/client/CMakeLists.txt
new file mode 100644
index 0000000..ad274b0
--- /dev/null
+++ b/channels/rail/client/CMakeLists.txt
@@ -0,0 +1,33 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("rail")
+
+set(${MODULE_PREFIX}_SRCS
+ ../rail_common.h
+ ../rail_common.c
+ client_rails.c
+ rail_main.c
+ rail_main.h
+ rail_orders.c
+ rail_orders.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx")
diff --git a/channels/rail/client/client_rails.c b/channels/rail/client/client_rails.c
new file mode 100644
index 0000000..ee6db44
--- /dev/null
+++ b/channels/rail/client/client_rails.c
@@ -0,0 +1,98 @@
+
+#include <freerdp/freerdp.h>
+#include <freerdp/client/rail.h>
+
+#include "rail_main.h"
+
+UINT client_rail_server_start_cmd(RailClientContext* context)
+{
+ UINT status = 0;
+ char argsAndFile[520] = { 0 };
+ RAIL_EXEC_ORDER exec = { 0 };
+ RAIL_SYSPARAM_ORDER sysparam = { 0 };
+ RAIL_CLIENT_STATUS_ORDER clientStatus = { 0 };
+
+ WINPR_ASSERT(context);
+ railPlugin* rail = context->handle;
+ WINPR_ASSERT(rail);
+ WINPR_ASSERT(rail->rdpcontext);
+
+ const rdpSettings* settings = rail->rdpcontext->settings;
+ WINPR_ASSERT(settings);
+
+ clientStatus.flags = TS_RAIL_CLIENTSTATUS_ALLOWLOCALMOVESIZE;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled))
+ clientStatus.flags |= TS_RAIL_CLIENTSTATUS_AUTORECONNECT;
+
+ clientStatus.flags |= TS_RAIL_CLIENTSTATUS_ZORDER_SYNC;
+ clientStatus.flags |= TS_RAIL_CLIENTSTATUS_WINDOW_RESIZE_MARGIN_SUPPORTED;
+ clientStatus.flags |= TS_RAIL_CLIENTSTATUS_APPBAR_REMOTING_SUPPORTED;
+ clientStatus.flags |= TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED;
+ clientStatus.flags |= TS_RAIL_CLIENTSTATUS_BIDIRECTIONAL_CLOAK_SUPPORTED;
+ status = context->ClientInformation(context, &clientStatus);
+
+ if (status != CHANNEL_RC_OK)
+ return status;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteAppLanguageBarSupported))
+ {
+ RAIL_LANGBAR_INFO_ORDER langBarInfo;
+ langBarInfo.languageBarStatus = 0x00000008; /* TF_SFT_HIDDEN */
+ status = context->ClientLanguageBarInfo(context, &langBarInfo);
+
+ /* We want the language bar, but the server might not support it. */
+ switch (status)
+ {
+ case CHANNEL_RC_OK:
+ case ERROR_BAD_CONFIGURATION:
+ break;
+ default:
+ return status;
+ }
+ }
+
+ sysparam.params = 0;
+ sysparam.params |= SPI_MASK_SET_HIGH_CONTRAST;
+ sysparam.highContrast.colorScheme.string = NULL;
+ sysparam.highContrast.colorScheme.length = 0;
+ sysparam.highContrast.flags = 0x7E;
+ sysparam.params |= SPI_MASK_SET_MOUSE_BUTTON_SWAP;
+ sysparam.mouseButtonSwap = FALSE;
+ sysparam.params |= SPI_MASK_SET_KEYBOARD_PREF;
+ sysparam.keyboardPref = FALSE;
+ sysparam.params |= SPI_MASK_SET_DRAG_FULL_WINDOWS;
+ sysparam.dragFullWindows = FALSE;
+ sysparam.params |= SPI_MASK_SET_KEYBOARD_CUES;
+ sysparam.keyboardCues = FALSE;
+ sysparam.params |= SPI_MASK_SET_WORK_AREA;
+ sysparam.workArea.left = 0;
+ sysparam.workArea.top = 0;
+ sysparam.workArea.right = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ sysparam.workArea.bottom = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ sysparam.dragFullWindows = FALSE;
+ status = context->ClientSystemParam(context, &sysparam);
+
+ if (status != CHANNEL_RC_OK)
+ return status;
+
+ const char* RemoteApplicationFile =
+ freerdp_settings_get_string(settings, FreeRDP_RemoteApplicationFile);
+ const char* RemoteApplicationCmdLine =
+ freerdp_settings_get_string(settings, FreeRDP_RemoteApplicationCmdLine);
+ if (RemoteApplicationFile && RemoteApplicationCmdLine)
+ {
+ _snprintf(argsAndFile, ARRAYSIZE(argsAndFile), "%s %s", RemoteApplicationCmdLine,
+ RemoteApplicationFile);
+ exec.RemoteApplicationArguments = argsAndFile;
+ }
+ else if (RemoteApplicationFile)
+ exec.RemoteApplicationArguments = RemoteApplicationFile;
+ else
+ exec.RemoteApplicationArguments = RemoteApplicationCmdLine;
+ exec.RemoteApplicationProgram =
+ freerdp_settings_get_string(settings, FreeRDP_RemoteApplicationProgram);
+ exec.RemoteApplicationWorkingDir =
+ freerdp_settings_get_string(settings, FreeRDP_ShellWorkingDirectory);
+ return context->ClientExecute(context, &exec);
+}
diff --git a/channels/rail/client/rail_main.c b/channels/rail/client/rail_main.c
new file mode 100644
index 0000000..c5828c1
--- /dev/null
+++ b/channels/rail/client/rail_main.c
@@ -0,0 +1,756 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RAIL Virtual Channel Plugin
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2011 Roman Barabanov <romanbarabanov@gmail.com>
+ * Copyright 2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2017 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2017 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+
+#include <freerdp/types.h>
+#include <freerdp/constants.h>
+#include <freerdp/freerdp.h>
+
+#include "rail_orders.h"
+#include "rail_main.h"
+
+#include "../../../channels/client/addin.h"
+
+RailClientContext* rail_get_client_interface(railPlugin* rail)
+{
+ RailClientContext* pInterface = NULL;
+
+ if (!rail)
+ return NULL;
+
+ pInterface = (RailClientContext*)rail->channelEntryPoints.pInterface;
+ return pInterface;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_send(railPlugin* rail, wStream* s)
+{
+ UINT status = 0;
+
+ if (!rail)
+ {
+ Stream_Free(s, TRUE);
+ return CHANNEL_RC_BAD_INIT_HANDLE;
+ }
+
+ status = rail->channelEntryPoints.pVirtualChannelWriteEx(
+ rail->InitHandle, rail->OpenHandle, Stream_Buffer(s), (UINT32)Stream_GetPosition(s), s);
+
+ if (status != CHANNEL_RC_OK)
+ {
+ Stream_Free(s, TRUE);
+ WLog_ERR(TAG, "pVirtualChannelWriteEx failed with %s [%08" PRIX32 "]",
+ WTSErrorToString(status), status);
+ }
+
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_send_channel_data(railPlugin* rail, wStream* src)
+{
+ wStream* s = NULL;
+ size_t length = 0;
+
+ if (!rail || !src)
+ return ERROR_INVALID_PARAMETER;
+
+ length = Stream_GetPosition(src);
+ s = Stream_New(NULL, length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write(s, Stream_Buffer(src), length);
+ return rail_send(rail, s);
+}
+
+/**
+ * Callback Interface
+ */
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_client_execute(RailClientContext* context, const RAIL_EXEC_ORDER* exec)
+{
+ char* exeOrFile = NULL;
+ UINT error = 0;
+ railPlugin* rail = NULL;
+ UINT16 flags = 0;
+ RAIL_UNICODE_STRING ruExeOrFile = { 0 };
+ RAIL_UNICODE_STRING ruWorkingDir = { 0 };
+ RAIL_UNICODE_STRING ruArguments = { 0 };
+
+ if (!context || !exec)
+ return ERROR_INVALID_PARAMETER;
+
+ rail = (railPlugin*)context->handle;
+ exeOrFile = exec->RemoteApplicationProgram;
+ flags = exec->flags;
+
+ if (!exeOrFile)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!utf8_string_to_rail_string(exec->RemoteApplicationProgram,
+ &ruExeOrFile) || /* RemoteApplicationProgram */
+ !utf8_string_to_rail_string(exec->RemoteApplicationWorkingDir,
+ &ruWorkingDir) || /* ShellWorkingDirectory */
+ !utf8_string_to_rail_string(exec->RemoteApplicationArguments,
+ &ruArguments)) /* RemoteApplicationCmdLine */
+ error = ERROR_INTERNAL_ERROR;
+ else
+ error = rail_send_client_exec_order(rail, flags, &ruExeOrFile, &ruWorkingDir, &ruArguments);
+
+ free(ruExeOrFile.string);
+ free(ruWorkingDir.string);
+ free(ruArguments.string);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_client_activate(RailClientContext* context, const RAIL_ACTIVATE_ORDER* activate)
+{
+ railPlugin* rail = NULL;
+
+ if (!context || !activate)
+ return ERROR_INVALID_PARAMETER;
+
+ rail = (railPlugin*)context->handle;
+ return rail_send_client_activate_order(rail, activate);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_send_client_sysparam(RailClientContext* context, RAIL_SYSPARAM_ORDER* sysparam)
+{
+ wStream* s = NULL;
+ size_t length = RAIL_SYSPARAM_ORDER_LENGTH;
+ railPlugin* rail = NULL;
+ UINT error = 0;
+ BOOL extendedSpiSupported = 0;
+
+ if (!context || !sysparam)
+ return ERROR_INVALID_PARAMETER;
+
+ rail = (railPlugin*)context->handle;
+
+ switch (sysparam->param)
+ {
+ case SPI_SET_DRAG_FULL_WINDOWS:
+ case SPI_SET_KEYBOARD_CUES:
+ case SPI_SET_KEYBOARD_PREF:
+ case SPI_SET_MOUSE_BUTTON_SWAP:
+ length += 1;
+ break;
+
+ case SPI_SET_WORK_AREA:
+ case SPI_DISPLAY_CHANGE:
+ case SPI_TASKBAR_POS:
+ length += 8;
+ break;
+
+ case SPI_SET_HIGH_CONTRAST:
+ length += sysparam->highContrast.colorSchemeLength + 10;
+ break;
+
+ case SPI_SETFILTERKEYS:
+ length += 20;
+ break;
+
+ case SPI_SETSTICKYKEYS:
+ case SPI_SETCARETWIDTH:
+ case SPI_SETTOGGLEKEYS:
+ length += 4;
+ break;
+
+ default:
+ return ERROR_BAD_ARGUMENTS;
+ }
+
+ s = rail_pdu_init(length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ extendedSpiSupported = rail_is_extended_spi_supported(rail->channelFlags);
+ if ((error = rail_write_sysparam_order(s, sysparam, extendedSpiSupported)))
+ {
+ WLog_ERR(TAG, "rail_write_client_sysparam_order failed with error %" PRIu32 "!", error);
+ Stream_Free(s, TRUE);
+ return error;
+ }
+
+ if ((error = rail_send_pdu(rail, s, TS_RAIL_ORDER_SYSPARAM)))
+ {
+ WLog_ERR(TAG, "rail_send_pdu failed with error %" PRIu32 "!", error);
+ }
+
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_client_system_param(RailClientContext* context,
+ const RAIL_SYSPARAM_ORDER* sysInParam)
+{
+ UINT error = CHANNEL_RC_OK;
+ RAIL_SYSPARAM_ORDER sysparam;
+
+ if (!context || !sysInParam)
+ return ERROR_INVALID_PARAMETER;
+
+ sysparam = *sysInParam;
+
+ if (sysparam.params & SPI_MASK_SET_HIGH_CONTRAST)
+ {
+ sysparam.param = SPI_SET_HIGH_CONTRAST;
+
+ if ((error = rail_send_client_sysparam(context, &sysparam)))
+ {
+ WLog_ERR(TAG, "rail_send_client_sysparam failed with error %" PRIu32 "!", error);
+ return error;
+ }
+ }
+
+ if (sysparam.params & SPI_MASK_TASKBAR_POS)
+ {
+ sysparam.param = SPI_TASKBAR_POS;
+
+ if ((error = rail_send_client_sysparam(context, &sysparam)))
+ {
+ WLog_ERR(TAG, "rail_send_client_sysparam failed with error %" PRIu32 "!", error);
+ return error;
+ }
+ }
+
+ if (sysparam.params & SPI_MASK_SET_MOUSE_BUTTON_SWAP)
+ {
+ sysparam.param = SPI_SET_MOUSE_BUTTON_SWAP;
+
+ if ((error = rail_send_client_sysparam(context, &sysparam)))
+ {
+ WLog_ERR(TAG, "rail_send_client_sysparam failed with error %" PRIu32 "!", error);
+ return error;
+ }
+ }
+
+ if (sysparam.params & SPI_MASK_SET_KEYBOARD_PREF)
+ {
+ sysparam.param = SPI_SET_KEYBOARD_PREF;
+
+ if ((error = rail_send_client_sysparam(context, &sysparam)))
+ {
+ WLog_ERR(TAG, "rail_send_client_sysparam failed with error %" PRIu32 "!", error);
+ return error;
+ }
+ }
+
+ if (sysparam.params & SPI_MASK_SET_DRAG_FULL_WINDOWS)
+ {
+ sysparam.param = SPI_SET_DRAG_FULL_WINDOWS;
+
+ if ((error = rail_send_client_sysparam(context, &sysparam)))
+ {
+ WLog_ERR(TAG, "rail_send_client_sysparam failed with error %" PRIu32 "!", error);
+ return error;
+ }
+ }
+
+ if (sysparam.params & SPI_MASK_SET_KEYBOARD_CUES)
+ {
+ sysparam.param = SPI_SET_KEYBOARD_CUES;
+
+ if ((error = rail_send_client_sysparam(context, &sysparam)))
+ {
+ WLog_ERR(TAG, "rail_send_client_sysparam failed with error %" PRIu32 "!", error);
+ return error;
+ }
+ }
+
+ if (sysparam.params & SPI_MASK_SET_WORK_AREA)
+ {
+ sysparam.param = SPI_SET_WORK_AREA;
+
+ if ((error = rail_send_client_sysparam(context, &sysparam)))
+ {
+ WLog_ERR(TAG, "rail_send_client_sysparam failed with error %" PRIu32 "!", error);
+ return error;
+ }
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_client_system_command(RailClientContext* context,
+ const RAIL_SYSCOMMAND_ORDER* syscommand)
+{
+ railPlugin* rail = NULL;
+
+ if (!context || !syscommand)
+ return ERROR_INVALID_PARAMETER;
+
+ rail = (railPlugin*)context->handle;
+ return rail_send_client_syscommand_order(rail, syscommand);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_client_handshake(RailClientContext* context, const RAIL_HANDSHAKE_ORDER* handshake)
+{
+ railPlugin* rail = NULL;
+
+ if (!context || !handshake)
+ return ERROR_INVALID_PARAMETER;
+
+ rail = (railPlugin*)context->handle;
+ return rail_send_handshake_order(rail, handshake);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_client_notify_event(RailClientContext* context,
+ const RAIL_NOTIFY_EVENT_ORDER* notifyEvent)
+{
+ railPlugin* rail = NULL;
+
+ if (!context || !notifyEvent)
+ return ERROR_INVALID_PARAMETER;
+
+ rail = (railPlugin*)context->handle;
+ return rail_send_client_notify_event_order(rail, notifyEvent);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_client_window_move(RailClientContext* context,
+ const RAIL_WINDOW_MOVE_ORDER* windowMove)
+{
+ railPlugin* rail = NULL;
+
+ if (!context || !windowMove)
+ return ERROR_INVALID_PARAMETER;
+
+ rail = (railPlugin*)context->handle;
+ return rail_send_client_window_move_order(rail, windowMove);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_client_information(RailClientContext* context,
+ const RAIL_CLIENT_STATUS_ORDER* clientStatus)
+{
+ railPlugin* rail = NULL;
+
+ if (!context || !clientStatus)
+ return ERROR_INVALID_PARAMETER;
+
+ rail = (railPlugin*)context->handle;
+ return rail_send_client_status_order(rail, clientStatus);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_client_system_menu(RailClientContext* context, const RAIL_SYSMENU_ORDER* sysmenu)
+{
+ railPlugin* rail = NULL;
+
+ if (!context || !sysmenu)
+ return ERROR_INVALID_PARAMETER;
+
+ rail = (railPlugin*)context->handle;
+ return rail_send_client_sysmenu_order(rail, sysmenu);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_client_language_bar_info(RailClientContext* context,
+ const RAIL_LANGBAR_INFO_ORDER* langBarInfo)
+{
+ railPlugin* rail = NULL;
+
+ if (!context || !langBarInfo)
+ return ERROR_INVALID_PARAMETER;
+
+ rail = (railPlugin*)context->handle;
+ return rail_send_client_langbar_info_order(rail, langBarInfo);
+}
+
+static UINT rail_client_language_ime_info(RailClientContext* context,
+ const RAIL_LANGUAGEIME_INFO_ORDER* langImeInfo)
+{
+ railPlugin* rail = NULL;
+
+ if (!context || !langImeInfo)
+ return ERROR_INVALID_PARAMETER;
+
+ rail = (railPlugin*)context->handle;
+ return rail_send_client_languageime_info_order(rail, langImeInfo);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_client_get_appid_request(RailClientContext* context,
+ const RAIL_GET_APPID_REQ_ORDER* getAppIdReq)
+{
+ railPlugin* rail = NULL;
+
+ if (!context || !getAppIdReq || !context->handle)
+ return ERROR_INVALID_PARAMETER;
+
+ rail = (railPlugin*)context->handle;
+ return rail_send_client_get_appid_req_order(rail, getAppIdReq);
+}
+
+static UINT rail_client_compartment_info(RailClientContext* context,
+ const RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo)
+{
+ railPlugin* rail = NULL;
+
+ if (!context || !compartmentInfo || !context->handle)
+ return ERROR_INVALID_PARAMETER;
+
+ rail = (railPlugin*)context->handle;
+ return rail_send_client_compartment_info_order(rail, compartmentInfo);
+}
+
+static UINT rail_client_cloak(RailClientContext* context, const RAIL_CLOAK* cloak)
+{
+ railPlugin* rail = NULL;
+
+ if (!context || !cloak || !context->handle)
+ return ERROR_INVALID_PARAMETER;
+
+ rail = (railPlugin*)context->handle;
+ return rail_send_client_cloak_order(rail, cloak);
+}
+
+static UINT rail_client_snap_arrange(RailClientContext* context, const RAIL_SNAP_ARRANGE* snap)
+{
+ railPlugin* rail = NULL;
+
+ if (!context || !snap || !context->handle)
+ return ERROR_INVALID_PARAMETER;
+
+ rail = (railPlugin*)context->handle;
+ return rail_send_client_snap_arrange_order(rail, snap);
+}
+
+static VOID VCAPITYPE rail_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle,
+ UINT event, LPVOID pData,
+ UINT32 dataLength, UINT32 totalLength,
+ UINT32 dataFlags)
+{
+ UINT error = CHANNEL_RC_OK;
+ railPlugin* rail = (railPlugin*)lpUserParam;
+
+ switch (event)
+ {
+ case CHANNEL_EVENT_DATA_RECEIVED:
+ if (!rail || (rail->OpenHandle != openHandle))
+ {
+ WLog_ERR(TAG, "error no match");
+ return;
+ }
+
+ if ((error = channel_client_post_message(rail->MsgsHandle, pData, dataLength,
+ totalLength, dataFlags)))
+ {
+ WLog_ERR(TAG,
+ "rail_virtual_channel_event_data_received"
+ " failed with error %" PRIu32 "!",
+ error);
+ }
+
+ break;
+
+ case CHANNEL_EVENT_WRITE_CANCELLED:
+ case CHANNEL_EVENT_WRITE_COMPLETE:
+ {
+ wStream* s = (wStream*)pData;
+ Stream_Free(s, TRUE);
+ }
+ break;
+
+ case CHANNEL_EVENT_USER:
+ break;
+ }
+
+ if (error && rail && rail->rdpcontext)
+ setChannelError(rail->rdpcontext, error,
+ "rail_virtual_channel_open_event reported an error");
+
+ return;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_virtual_channel_event_connected(railPlugin* rail, LPVOID pData, UINT32 dataLength)
+{
+ RailClientContext* context = rail_get_client_interface(rail);
+ UINT status = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(rail);
+
+ if (context)
+ {
+ IFCALLRET(context->OnOpen, status, context, &rail->sendHandshake);
+
+ if (status != CHANNEL_RC_OK)
+ WLog_ERR(TAG, "context->OnOpen failed with %s [%08" PRIX32 "]",
+ WTSErrorToString(status), status);
+ }
+ rail->MsgsHandle = channel_client_create_handler(rail->rdpcontext, rail, rail_order_recv,
+ RAIL_SVC_CHANNEL_NAME);
+ if (!rail->MsgsHandle)
+ return ERROR_INTERNAL_ERROR;
+
+ return rail->channelEntryPoints.pVirtualChannelOpenEx(rail->InitHandle, &rail->OpenHandle,
+ rail->channelDef.name,
+ rail_virtual_channel_open_event_ex);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_virtual_channel_event_disconnected(railPlugin* rail)
+{
+ UINT rc = 0;
+
+ channel_client_quit_handler(rail->MsgsHandle);
+ if (rail->OpenHandle == 0)
+ return CHANNEL_RC_OK;
+
+ WINPR_ASSERT(rail->channelEntryPoints.pVirtualChannelCloseEx);
+ rc = rail->channelEntryPoints.pVirtualChannelCloseEx(rail->InitHandle, rail->OpenHandle);
+
+ if (CHANNEL_RC_OK != rc)
+ {
+ WLog_ERR(TAG, "pVirtualChannelCloseEx failed with %s [%08" PRIX32 "]", WTSErrorToString(rc),
+ rc);
+ return rc;
+ }
+
+ rail->OpenHandle = 0;
+
+ return CHANNEL_RC_OK;
+}
+
+static void rail_virtual_channel_event_terminated(railPlugin* rail)
+{
+ rail->InitHandle = 0;
+ free(rail->context);
+ free(rail);
+}
+
+static VOID VCAPITYPE rail_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle,
+ UINT event, LPVOID pData, UINT dataLength)
+{
+ UINT error = CHANNEL_RC_OK;
+ railPlugin* rail = (railPlugin*)lpUserParam;
+
+ if (!rail || (rail->InitHandle != pInitHandle))
+ {
+ WLog_ERR(TAG, "error no match");
+ return;
+ }
+
+ switch (event)
+ {
+ case CHANNEL_EVENT_CONNECTED:
+ if ((error = rail_virtual_channel_event_connected(rail, pData, dataLength)))
+ WLog_ERR(TAG, "rail_virtual_channel_event_connected failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_DISCONNECTED:
+ if ((error = rail_virtual_channel_event_disconnected(rail)))
+ WLog_ERR(TAG,
+ "rail_virtual_channel_event_disconnected failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_TERMINATED:
+ rail_virtual_channel_event_terminated(rail);
+ break;
+
+ case CHANNEL_EVENT_ATTACHED:
+ case CHANNEL_EVENT_DETACHED:
+ default:
+ break;
+ }
+
+ if (error && rail->rdpcontext)
+ setChannelError(rail->rdpcontext, error,
+ "rail_virtual_channel_init_event_ex reported an error");
+}
+
+/* rail is always built-in */
+#define VirtualChannelEntryEx rail_VirtualChannelEntryEx
+
+FREERDP_ENTRY_POINT(BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS pEntryPoints,
+ PVOID pInitHandle))
+{
+ UINT rc = 0;
+ railPlugin* rail = NULL;
+ RailClientContext* context = NULL;
+ CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx = NULL;
+ BOOL isFreerdp = FALSE;
+ rail = (railPlugin*)calloc(1, sizeof(railPlugin));
+
+ if (!rail)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return FALSE;
+ }
+
+ /* Default to automatically replying to server handshakes */
+ rail->sendHandshake = TRUE;
+ rail->channelDef.options = CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP |
+ CHANNEL_OPTION_COMPRESS_RDP | CHANNEL_OPTION_SHOW_PROTOCOL;
+ sprintf_s(rail->channelDef.name, ARRAYSIZE(rail->channelDef.name), RAIL_SVC_CHANNEL_NAME);
+ pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints;
+
+ if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) &&
+ (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER))
+ {
+ context = (RailClientContext*)calloc(1, sizeof(RailClientContext));
+
+ if (!context)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ free(rail);
+ return FALSE;
+ }
+
+ context->handle = (void*)rail;
+ context->custom = NULL;
+ context->ClientExecute = rail_client_execute;
+ context->ClientActivate = rail_client_activate;
+ context->ClientSystemParam = rail_client_system_param;
+ context->ClientSystemCommand = rail_client_system_command;
+ context->ClientHandshake = rail_client_handshake;
+ context->ClientNotifyEvent = rail_client_notify_event;
+ context->ClientWindowMove = rail_client_window_move;
+ context->ClientInformation = rail_client_information;
+ context->ClientSystemMenu = rail_client_system_menu;
+ context->ClientLanguageBarInfo = rail_client_language_bar_info;
+ context->ClientLanguageIMEInfo = rail_client_language_ime_info;
+ context->ClientGetAppIdRequest = rail_client_get_appid_request;
+ context->ClientSnapArrange = rail_client_snap_arrange;
+ context->ClientCloak = rail_client_cloak;
+ context->ClientCompartmentInfo = rail_client_compartment_info;
+ rail->rdpcontext = pEntryPointsEx->context;
+ rail->context = context;
+ isFreerdp = TRUE;
+ }
+
+ rail->log = WLog_Get("com.freerdp.channels.rail.client");
+ WLog_Print(rail->log, WLOG_DEBUG, "VirtualChannelEntryEx");
+ CopyMemory(&(rail->channelEntryPoints), pEntryPoints, sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX));
+ rail->InitHandle = pInitHandle;
+ rc = rail->channelEntryPoints.pVirtualChannelInitEx(
+ rail, context, pInitHandle, &rail->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000,
+ rail_virtual_channel_init_event_ex);
+
+ if (CHANNEL_RC_OK != rc)
+ {
+ WLog_ERR(TAG, "failed with %s [%08" PRIX32 "]", WTSErrorToString(rc), rc);
+ goto error_out;
+ }
+
+ rail->channelEntryPoints.pInterface = context;
+ return TRUE;
+error_out:
+
+ if (isFreerdp)
+ free(rail->context);
+
+ free(rail);
+ return FALSE;
+}
diff --git a/channels/rail/client/rail_main.h b/channels/rail/client/rail_main.h
new file mode 100644
index 0000000..2cbc6c1
--- /dev/null
+++ b/channels/rail/client/rail_main.h
@@ -0,0 +1,60 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RAIL Virtual Channel Plugin
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2011 Roman Barabanov <romanbarabanov@gmail.com>
+ * Copyright 2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RAIL_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_RAIL_CLIENT_MAIN_H
+
+#include <freerdp/rail.h>
+#include <freerdp/svc.h>
+#include <freerdp/addin.h>
+#include <freerdp/settings.h>
+#include <freerdp/client/rail.h>
+
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+#include <winpr/stream.h>
+
+#include "../rail_common.h"
+
+typedef struct
+{
+ CHANNEL_DEF channelDef;
+ CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints;
+
+ RailClientContext* context;
+
+ wLog* log;
+ void* InitHandle;
+ DWORD OpenHandle;
+ void* MsgsHandle;
+ rdpContext* rdpcontext;
+ DWORD channelBuildNumber;
+ DWORD channelFlags;
+ RAIL_CLIENT_STATUS_ORDER clientStatus;
+ BOOL sendHandshake;
+} railPlugin;
+
+RailClientContext* rail_get_client_interface(railPlugin* rail);
+UINT rail_send_channel_data(railPlugin* rail, wStream* s);
+
+#endif /* FREERDP_CHANNEL_RAIL_CLIENT_MAIN_H */
diff --git a/channels/rail/client/rail_orders.c b/channels/rail/client/rail_orders.c
new file mode 100644
index 0000000..7ac432e
--- /dev/null
+++ b/channels/rail/client/rail_orders.c
@@ -0,0 +1,1564 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Remote Applications Integrated Locally (RAIL) Orders
+ *
+ * Copyright 2009 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2011 Roman Barabanov <romanbarabanov@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2017 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2017 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+
+#include <freerdp/channels/log.h>
+#include <freerdp/freerdp.h>
+
+#include "rail_orders.h"
+
+static BOOL rail_is_feature_supported(const rdpContext* context, UINT32 featureMask);
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_send_pdu(railPlugin* rail, wStream* s, UINT16 orderType)
+{
+ char buffer[128] = { 0 };
+ UINT16 orderLength = 0;
+
+ if (!rail || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ orderLength = (UINT16)Stream_GetPosition(s);
+ Stream_SetPosition(s, 0);
+ rail_write_pdu_header(s, orderType, orderLength);
+ Stream_SetPosition(s, orderLength);
+ WLog_Print(rail->log, WLOG_DEBUG, "Sending %s PDU, length: %" PRIu16 "",
+ rail_get_order_type_string_full(orderType, buffer, sizeof(buffer)), orderLength);
+ return rail_send_channel_data(rail, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_server_exec_result_order(wStream* s, RAIL_EXEC_RESULT_ORDER* execResult)
+{
+ if (!s || !execResult)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_EXEC_RESULT_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, execResult->flags); /* flags (2 bytes) */
+ Stream_Read_UINT16(s, execResult->execResult); /* execResult (2 bytes) */
+ Stream_Read_UINT32(s, execResult->rawResult); /* rawResult (4 bytes) */
+ Stream_Seek_UINT16(s); /* padding (2 bytes) */
+ return rail_read_unicode_string(s, &execResult->exeOrFile)
+ ? CHANNEL_RC_OK
+ : ERROR_INTERNAL_ERROR; /* exeOrFile */
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_server_minmaxinfo_order(wStream* s, RAIL_MINMAXINFO_ORDER* minmaxinfo)
+{
+ if (!s || !minmaxinfo)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_MINMAXINFO_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, minmaxinfo->windowId); /* windowId (4 bytes) */
+ Stream_Read_INT16(s, minmaxinfo->maxWidth); /* maxWidth (2 bytes) */
+ Stream_Read_INT16(s, minmaxinfo->maxHeight); /* maxHeight (2 bytes) */
+ Stream_Read_INT16(s, minmaxinfo->maxPosX); /* maxPosX (2 bytes) */
+ Stream_Read_INT16(s, minmaxinfo->maxPosY); /* maxPosY (2 bytes) */
+ Stream_Read_INT16(s, minmaxinfo->minTrackWidth); /* minTrackWidth (2 bytes) */
+ Stream_Read_INT16(s, minmaxinfo->minTrackHeight); /* minTrackHeight (2 bytes) */
+ Stream_Read_INT16(s, minmaxinfo->maxTrackWidth); /* maxTrackWidth (2 bytes) */
+ Stream_Read_INT16(s, minmaxinfo->maxTrackHeight); /* maxTrackHeight (2 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_server_localmovesize_order(wStream* s,
+ RAIL_LOCALMOVESIZE_ORDER* localMoveSize)
+{
+ UINT16 isMoveSizeStart = 0;
+
+ if (!s || !localMoveSize)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_LOCALMOVESIZE_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, localMoveSize->windowId); /* windowId (4 bytes) */
+ Stream_Read_UINT16(s, isMoveSizeStart); /* isMoveSizeStart (2 bytes) */
+ localMoveSize->isMoveSizeStart = (isMoveSizeStart != 0) ? TRUE : FALSE;
+ Stream_Read_UINT16(s, localMoveSize->moveSizeType); /* moveSizeType (2 bytes) */
+ Stream_Read_INT16(s, localMoveSize->posX); /* posX (2 bytes) */
+ Stream_Read_INT16(s, localMoveSize->posY); /* posY (2 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_server_get_appid_resp_order(wStream* s,
+ RAIL_GET_APPID_RESP_ORDER* getAppidResp)
+{
+ if (!s || !getAppidResp)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_GET_APPID_RESP_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, getAppidResp->windowId); /* windowId (4 bytes) */
+ Stream_Read_UTF16_String(
+ s, getAppidResp->applicationId,
+ ARRAYSIZE(getAppidResp->applicationId)); /* applicationId (260 UNICODE chars) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_langbar_info_order(wStream* s, RAIL_LANGBAR_INFO_ORDER* langbarInfo)
+{
+ if (!s || !langbarInfo)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_LANGBAR_INFO_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, langbarInfo->languageBarStatus); /* languageBarStatus (4 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+static UINT rail_write_client_status_order(wStream* s, const RAIL_CLIENT_STATUS_ORDER* clientStatus)
+{
+ if (!s || !clientStatus)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, clientStatus->flags); /* flags (4 bytes) */
+ return ERROR_SUCCESS;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_write_client_exec_order(wStream* s, UINT16 flags,
+ const RAIL_UNICODE_STRING* exeOrFile,
+ const RAIL_UNICODE_STRING* workingDir,
+ const RAIL_UNICODE_STRING* arguments)
+{
+ UINT error = 0;
+
+ if (!s || !exeOrFile || !workingDir || !arguments)
+ return ERROR_INVALID_PARAMETER;
+
+ /* [MS-RDPERP] 2.2.2.3.1 Client Execute PDU (TS_RAIL_ORDER_EXEC)
+ * Check argument limits */
+ if ((exeOrFile->length > 520) || (workingDir->length > 520) || (arguments->length > 16000))
+ {
+ WLog_ERR(TAG,
+ "TS_RAIL_ORDER_EXEC argument limits exceeded: ExeOrFile=%" PRIu16
+ " [max=520], WorkingDir=%" PRIu16 " [max=520], Arguments=%" PRIu16 " [max=16000]",
+ exeOrFile->length, workingDir->length, arguments->length);
+ return ERROR_BAD_ARGUMENTS;
+ }
+
+ Stream_Write_UINT16(s, flags); /* flags (2 bytes) */
+ Stream_Write_UINT16(s, exeOrFile->length); /* exeOrFileLength (2 bytes) */
+ Stream_Write_UINT16(s, workingDir->length); /* workingDirLength (2 bytes) */
+ Stream_Write_UINT16(s, arguments->length); /* argumentsLength (2 bytes) */
+
+ if ((error = rail_write_unicode_string_value(s, exeOrFile)))
+ {
+ WLog_ERR(TAG, "rail_write_unicode_string_value failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ if ((error = rail_write_unicode_string_value(s, workingDir)))
+ {
+ WLog_ERR(TAG, "rail_write_unicode_string_value failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ if ((error = rail_write_unicode_string_value(s, arguments)))
+ {
+ WLog_ERR(TAG, "rail_write_unicode_string_value failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ return error;
+}
+
+static UINT rail_write_client_activate_order(wStream* s, const RAIL_ACTIVATE_ORDER* activate)
+{
+ BYTE enabled = 0;
+
+ if (!s || !activate)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, activate->windowId); /* windowId (4 bytes) */
+ enabled = activate->enabled ? 1 : 0;
+ Stream_Write_UINT8(s, enabled); /* enabled (1 byte) */
+ return ERROR_SUCCESS;
+}
+
+static UINT rail_write_client_sysmenu_order(wStream* s, const RAIL_SYSMENU_ORDER* sysmenu)
+{
+ if (!s || !sysmenu)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, sysmenu->windowId); /* windowId (4 bytes) */
+ Stream_Write_INT16(s, sysmenu->left); /* left (2 bytes) */
+ Stream_Write_INT16(s, sysmenu->top); /* top (2 bytes) */
+ return ERROR_SUCCESS;
+}
+
+static UINT rail_write_client_syscommand_order(wStream* s, const RAIL_SYSCOMMAND_ORDER* syscommand)
+{
+ if (!s || !syscommand)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, syscommand->windowId); /* windowId (4 bytes) */
+ Stream_Write_UINT16(s, syscommand->command); /* command (2 bytes) */
+ return ERROR_SUCCESS;
+}
+
+static UINT rail_write_client_notify_event_order(wStream* s,
+ const RAIL_NOTIFY_EVENT_ORDER* notifyEvent)
+{
+ if (!s || !notifyEvent)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, notifyEvent->windowId); /* windowId (4 bytes) */
+ Stream_Write_UINT32(s, notifyEvent->notifyIconId); /* notifyIconId (4 bytes) */
+ Stream_Write_UINT32(s, notifyEvent->message); /* notifyIconId (4 bytes) */
+ return ERROR_SUCCESS;
+}
+
+static UINT rail_write_client_window_move_order(wStream* s,
+ const RAIL_WINDOW_MOVE_ORDER* windowMove)
+{
+ if (!s || !windowMove)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, windowMove->windowId); /* windowId (4 bytes) */
+ Stream_Write_INT16(s, windowMove->left); /* left (2 bytes) */
+ Stream_Write_INT16(s, windowMove->top); /* top (2 bytes) */
+ Stream_Write_INT16(s, windowMove->right); /* right (2 bytes) */
+ Stream_Write_INT16(s, windowMove->bottom); /* bottom (2 bytes) */
+ return ERROR_SUCCESS;
+}
+
+static UINT rail_write_client_get_appid_req_order(wStream* s,
+ const RAIL_GET_APPID_REQ_ORDER* getAppidReq)
+{
+ if (!s || !getAppidReq)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, getAppidReq->windowId); /* windowId (4 bytes) */
+ return ERROR_SUCCESS;
+}
+
+static UINT rail_write_langbar_info_order(wStream* s, const RAIL_LANGBAR_INFO_ORDER* langbarInfo)
+{
+ if (!s || !langbarInfo)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, langbarInfo->languageBarStatus); /* languageBarStatus (4 bytes) */
+ return ERROR_SUCCESS;
+}
+
+static UINT rail_write_languageime_info_order(wStream* s,
+ const RAIL_LANGUAGEIME_INFO_ORDER* langImeInfo)
+{
+ if (!s || !langImeInfo)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, langImeInfo->ProfileType);
+ Stream_Write_UINT16(s, langImeInfo->LanguageID);
+ Stream_Write(s, &langImeInfo->LanguageProfileCLSID, sizeof(langImeInfo->LanguageProfileCLSID));
+ Stream_Write(s, &langImeInfo->ProfileGUID, sizeof(langImeInfo->ProfileGUID));
+ Stream_Write_UINT32(s, langImeInfo->KeyboardLayout);
+ return ERROR_SUCCESS;
+}
+
+static UINT rail_write_compartment_info_order(wStream* s,
+ const RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo)
+{
+ if (!s || !compartmentInfo)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, compartmentInfo->ImeState);
+ Stream_Write_UINT32(s, compartmentInfo->ImeConvMode);
+ Stream_Write_UINT32(s, compartmentInfo->ImeSentenceMode);
+ Stream_Write_UINT32(s, compartmentInfo->KanaMode);
+ return ERROR_SUCCESS;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_handshake_order(railPlugin* rail, wStream* s)
+{
+ RailClientContext* context = rail_get_client_interface(rail);
+ RAIL_HANDSHAKE_ORDER serverHandshake = { 0 };
+ UINT error = 0;
+
+ if (!context || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_handshake_order(s, &serverHandshake)))
+ {
+ WLog_ERR(TAG, "rail_read_handshake_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ rail->channelBuildNumber = serverHandshake.buildNumber;
+
+ if (rail->sendHandshake)
+ {
+ RAIL_HANDSHAKE_ORDER clientHandshake = { 0 };
+ clientHandshake.buildNumber = 0x00001DB0;
+ error = context->ClientHandshake(context, &clientHandshake);
+ }
+
+ if (error != CHANNEL_RC_OK)
+ return error;
+
+ if (context->custom)
+ {
+ IFCALLRET(context->ServerHandshake, error, context, &serverHandshake);
+
+ if (error)
+ WLog_ERR(TAG, "context.ServerHandshake failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+static UINT rail_read_compartment_info_order(wStream* s,
+ RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_COMPARTMENT_INFO_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, compartmentInfo->ImeState); /* ImeState (4 bytes) */
+ Stream_Read_UINT32(s, compartmentInfo->ImeConvMode); /* ImeConvMode (4 bytes) */
+ Stream_Read_UINT32(s, compartmentInfo->ImeSentenceMode); /* ImeSentenceMode (4 bytes) */
+ Stream_Read_UINT32(s, compartmentInfo->KanaMode); /* KANAMode (4 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+static UINT rail_recv_compartmentinfo_order(railPlugin* rail, wStream* s)
+{
+ RailClientContext* context = rail_get_client_interface(rail);
+ RAIL_COMPARTMENT_INFO_ORDER pdu = { 0 };
+ UINT error = 0;
+
+ if (!context || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED))
+ return ERROR_BAD_CONFIGURATION;
+
+ if ((error = rail_read_compartment_info_order(s, &pdu)))
+ return error;
+
+ if (context->custom)
+ {
+ IFCALLRET(context->ClientCompartmentInfo, error, context, &pdu);
+
+ if (error)
+ WLog_ERR(TAG, "context.ClientCompartmentInfo failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+BOOL rail_is_feature_supported(const rdpContext* context, UINT32 featureMask)
+{
+ UINT32 supported = 0;
+ UINT32 masked = 0;
+
+ if (!context || !context->settings)
+ return FALSE;
+
+ const UINT32 level =
+ freerdp_settings_get_uint32(context->settings, FreeRDP_RemoteApplicationSupportLevel);
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->settings, FreeRDP_RemoteApplicationSupportMask);
+ supported = level & mask;
+ masked = (supported & featureMask);
+
+ if (masked != featureMask)
+ {
+ char maskstr[256] = { 0 };
+ char actualstr[256] = { 0 };
+
+ WLog_WARN(TAG, "have %s, require %s",
+ freerdp_rail_support_flags_to_string(supported, actualstr, sizeof(actualstr)),
+ freerdp_rail_support_flags_to_string(featureMask, maskstr, sizeof(maskstr)));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_handshake_ex_order(railPlugin* rail, wStream* s)
+{
+ RailClientContext* context = rail_get_client_interface(rail);
+ RAIL_HANDSHAKE_EX_ORDER serverHandshake = { 0 };
+ UINT error = 0;
+
+ if (!rail || !context || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_HANDSHAKE_EX_SUPPORTED))
+ return ERROR_BAD_CONFIGURATION;
+
+ if ((error = rail_read_handshake_ex_order(s, &serverHandshake)))
+ {
+ WLog_ERR(TAG, "rail_read_handshake_ex_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ rail->channelBuildNumber = serverHandshake.buildNumber;
+ rail->channelFlags = serverHandshake.railHandshakeFlags;
+
+ if (rail->sendHandshake)
+ {
+ RAIL_HANDSHAKE_ORDER clientHandshake = { 0 };
+ clientHandshake.buildNumber = 0x00001DB0;
+ /* 2.2.2.2.3 HandshakeEx PDU (TS_RAIL_ORDER_HANDSHAKE_EX)
+ * Client response is really a Handshake PDU */
+ error = context->ClientHandshake(context, &clientHandshake);
+ }
+
+ if (error != CHANNEL_RC_OK)
+ return error;
+
+ if (context->custom)
+ {
+ IFCALLRET(context->ServerHandshakeEx, error, context, &serverHandshake);
+
+ if (error)
+ WLog_ERR(TAG, "context.ServerHandshakeEx failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_exec_result_order(railPlugin* rail, wStream* s)
+{
+ RailClientContext* context = rail_get_client_interface(rail);
+ RAIL_EXEC_RESULT_ORDER execResult = { 0 };
+ UINT error = 0;
+
+ if (!context || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_server_exec_result_order(s, &execResult)))
+ {
+ WLog_ERR(TAG, "rail_read_server_exec_result_order failed with error %" PRIu32 "!", error);
+ goto fail;
+ }
+
+ if (context->custom)
+ {
+ IFCALLRET(context->ServerExecuteResult, error, context, &execResult);
+
+ if (error)
+ WLog_ERR(TAG, "context.ServerExecuteResult failed with error %" PRIu32 "", error);
+ }
+
+fail:
+ free(execResult.exeOrFile.string);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_server_sysparam_order(railPlugin* rail, wStream* s)
+{
+ RailClientContext* context = rail_get_client_interface(rail);
+ RAIL_SYSPARAM_ORDER sysparam;
+ UINT error = 0;
+ BOOL extendedSpiSupported = 0;
+
+ if (!context || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ extendedSpiSupported = rail_is_extended_spi_supported(rail->channelFlags);
+ if ((error = rail_read_sysparam_order(s, &sysparam, extendedSpiSupported)))
+ {
+ WLog_ERR(TAG, "rail_read_sysparam_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ if (context->custom)
+ {
+ IFCALLRET(context->ServerSystemParam, error, context, &sysparam);
+
+ if (error)
+ WLog_ERR(TAG, "context.ServerSystemParam failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_server_minmaxinfo_order(railPlugin* rail, wStream* s)
+{
+ RailClientContext* context = rail_get_client_interface(rail);
+ RAIL_MINMAXINFO_ORDER minMaxInfo = { 0 };
+ UINT error = 0;
+
+ if (!context || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_server_minmaxinfo_order(s, &minMaxInfo)))
+ {
+ WLog_ERR(TAG, "rail_read_server_minmaxinfo_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ if (context->custom)
+ {
+ IFCALLRET(context->ServerMinMaxInfo, error, context, &minMaxInfo);
+
+ if (error)
+ WLog_ERR(TAG, "context.ServerMinMaxInfo failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_server_localmovesize_order(railPlugin* rail, wStream* s)
+{
+ RailClientContext* context = rail_get_client_interface(rail);
+ RAIL_LOCALMOVESIZE_ORDER localMoveSize = { 0 };
+ UINT error = 0;
+
+ if (!context || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_server_localmovesize_order(s, &localMoveSize)))
+ {
+ WLog_ERR(TAG, "rail_read_server_localmovesize_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ if (context->custom)
+ {
+ IFCALLRET(context->ServerLocalMoveSize, error, context, &localMoveSize);
+
+ if (error)
+ WLog_ERR(TAG, "context.ServerLocalMoveSize failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_server_get_appid_resp_order(railPlugin* rail, wStream* s)
+{
+ RailClientContext* context = rail_get_client_interface(rail);
+ RAIL_GET_APPID_RESP_ORDER getAppIdResp = { 0 };
+ UINT error = 0;
+
+ if (!context || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_server_get_appid_resp_order(s, &getAppIdResp)))
+ {
+ WLog_ERR(TAG, "rail_read_server_get_appid_resp_order failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ if (context->custom)
+ {
+ IFCALLRET(context->ServerGetAppIdResponse, error, context, &getAppIdResp);
+
+ if (error)
+ WLog_ERR(TAG, "context.ServerGetAppIdResponse failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_langbar_info_order(railPlugin* rail, wStream* s)
+{
+ RailClientContext* context = rail_get_client_interface(rail);
+ RAIL_LANGBAR_INFO_ORDER langBarInfo = { 0 };
+ UINT error = 0;
+
+ if (!context)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_DOCKED_LANGBAR_SUPPORTED))
+ return ERROR_BAD_CONFIGURATION;
+
+ if ((error = rail_read_langbar_info_order(s, &langBarInfo)))
+ {
+ WLog_ERR(TAG, "rail_read_langbar_info_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ if (context->custom)
+ {
+ IFCALLRET(context->ServerLanguageBarInfo, error, context, &langBarInfo);
+
+ if (error)
+ WLog_ERR(TAG, "context.ServerLanguageBarInfo failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+static UINT rail_read_taskbar_info_order(wStream* s, RAIL_TASKBAR_INFO_ORDER* taskbarInfo)
+{
+ if (!s || !taskbarInfo)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_TASKBAR_INFO_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, taskbarInfo->TaskbarMessage);
+ Stream_Read_UINT32(s, taskbarInfo->WindowIdTab);
+ Stream_Read_UINT32(s, taskbarInfo->Body);
+ return CHANNEL_RC_OK;
+}
+
+static UINT rail_recv_taskbar_info_order(railPlugin* rail, wStream* s)
+{
+ RailClientContext* context = rail_get_client_interface(rail);
+ RAIL_TASKBAR_INFO_ORDER taskBarInfo = { 0 };
+ UINT error = 0;
+
+ if (!context)
+ return ERROR_INVALID_PARAMETER;
+
+ /* 2.2.2.14.1 Taskbar Tab Info PDU (TS_RAIL_ORDER_TASKBARINFO)
+ * server -> client message only supported if announced. */
+ if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_SHELL_INTEGRATION_SUPPORTED))
+ return ERROR_BAD_CONFIGURATION;
+
+ if ((error = rail_read_taskbar_info_order(s, &taskBarInfo)))
+ {
+ WLog_ERR(TAG, "rail_read_langbar_info_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ if (context->custom)
+ {
+ IFCALLRET(context->ServerTaskBarInfo, error, context, &taskBarInfo);
+
+ if (error)
+ WLog_ERR(TAG, "context.ServerTaskBarInfo failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+static UINT rail_read_zorder_sync_order(wStream* s, RAIL_ZORDER_SYNC* zorder)
+{
+ if (!s || !zorder)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_Z_ORDER_SYNC_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, zorder->windowIdMarker);
+ return CHANNEL_RC_OK;
+}
+
+static UINT rail_recv_zorder_sync_order(railPlugin* rail, wStream* s)
+{
+ RailClientContext* context = rail_get_client_interface(rail);
+ RAIL_ZORDER_SYNC zorder = { 0 };
+ UINT error = 0;
+
+ if (!context)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((rail->clientStatus.flags & TS_RAIL_CLIENTSTATUS_ZORDER_SYNC) == 0)
+ return ERROR_INVALID_DATA;
+
+ if ((error = rail_read_zorder_sync_order(s, &zorder)))
+ {
+ WLog_ERR(TAG, "rail_read_zorder_sync_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ if (context->custom)
+ {
+ IFCALLRET(context->ServerZOrderSync, error, context, &zorder);
+
+ if (error)
+ WLog_ERR(TAG, "context.ServerZOrderSync failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+static UINT rail_read_cloak_order(wStream* s, RAIL_CLOAK* cloak)
+{
+ BYTE cloaked = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_CLOAK_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, cloak->windowId); /* WindowId (4 bytes) */
+ Stream_Read_UINT8(s, cloaked); /* Cloaked (1 byte) */
+ cloak->cloak = (cloaked != 0) ? TRUE : FALSE;
+ return CHANNEL_RC_OK;
+}
+
+static UINT rail_recv_cloak_order(railPlugin* rail, wStream* s)
+{
+ RailClientContext* context = rail_get_client_interface(rail);
+ RAIL_CLOAK cloak = { 0 };
+ UINT error = 0;
+
+ if (!context)
+ return ERROR_INVALID_PARAMETER;
+
+ /* 2.2.2.12.1 Window Cloak State Change PDU (TS_RAIL_ORDER_CLOAK)
+ * server -> client message only supported if announced. */
+ if ((rail->clientStatus.flags & TS_RAIL_CLIENTSTATUS_BIDIRECTIONAL_CLOAK_SUPPORTED) == 0)
+ return ERROR_INVALID_DATA;
+
+ if ((error = rail_read_cloak_order(s, &cloak)))
+ {
+ WLog_ERR(TAG, "rail_read_zorder_sync_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ if (context->custom)
+ {
+ IFCALLRET(context->ServerCloak, error, context, &cloak);
+
+ if (error)
+ WLog_ERR(TAG, "context.ServerZOrderSync failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+static UINT rail_read_power_display_request_order(wStream* s, RAIL_POWER_DISPLAY_REQUEST* power)
+{
+ UINT32 active = 0;
+
+ if (!s || !power)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_POWER_DISPLAY_REQUEST_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, active);
+ power->active = active != 0;
+ return CHANNEL_RC_OK;
+}
+
+static UINT rail_recv_power_display_request_order(railPlugin* rail, wStream* s)
+{
+ RailClientContext* context = rail_get_client_interface(rail);
+ RAIL_POWER_DISPLAY_REQUEST power = { 0 };
+ UINT error = 0;
+
+ if (!context)
+ return ERROR_INVALID_PARAMETER;
+
+ /* 2.2.2.13.1 Power Display Request PDU(TS_RAIL_ORDER_POWER_DISPLAY_REQUEST)
+ */
+ if ((rail->clientStatus.flags & TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED) == 0)
+ return ERROR_INVALID_DATA;
+
+ if ((error = rail_read_power_display_request_order(s, &power)))
+ {
+ WLog_ERR(TAG, "rail_read_zorder_sync_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ if (context->custom)
+ {
+ IFCALLRET(context->ServerPowerDisplayRequest, error, context, &power);
+
+ if (error)
+ WLog_ERR(TAG, "context.ServerPowerDisplayRequest failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+static UINT rail_read_get_application_id_extended_response_order(wStream* s,
+ RAIL_GET_APPID_RESP_EX* id)
+{
+ if (!s || !id)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, id->windowID);
+
+ if (!Stream_Read_UTF16_String(s, id->applicationID, ARRAYSIZE(id->applicationID)))
+ return ERROR_INVALID_DATA;
+
+ if (_wcsnlen(id->applicationID, ARRAYSIZE(id->applicationID)) >= ARRAYSIZE(id->applicationID))
+ return ERROR_INVALID_DATA;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, id->processId);
+
+ if (!Stream_Read_UTF16_String(s, id->processImageName, ARRAYSIZE(id->processImageName)))
+ return ERROR_INVALID_DATA;
+
+ if (_wcsnlen(id->applicationID, ARRAYSIZE(id->processImageName)) >=
+ ARRAYSIZE(id->processImageName))
+ return ERROR_INVALID_DATA;
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rail_recv_get_application_id_extended_response_order(railPlugin* rail, wStream* s)
+{
+ RailClientContext* context = rail_get_client_interface(rail);
+ RAIL_GET_APPID_RESP_EX id = { 0 };
+ UINT error = 0;
+
+ if (!context)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_get_application_id_extended_response_order(s, &id)))
+ {
+ WLog_ERR(TAG,
+ "rail_read_get_application_id_extended_response_order failed with error %" PRIu32
+ "!",
+ error);
+ return error;
+ }
+
+ if (context->custom)
+ {
+ IFCALLRET(context->ServerGetAppidResponseExtended, error, context, &id);
+
+ if (error)
+ WLog_ERR(TAG, "context.ServerGetAppidResponseExtended failed with error %" PRIu32 "",
+ error);
+ }
+
+ return error;
+}
+
+static UINT rail_read_textscaleinfo_order(wStream* s, UINT32* pTextScaleFactor)
+{
+ WINPR_ASSERT(pTextScaleFactor);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, *pTextScaleFactor);
+ return CHANNEL_RC_OK;
+}
+
+static UINT rail_recv_textscaleinfo_order(railPlugin* rail, wStream* s)
+{
+ RailClientContext* context = rail_get_client_interface(rail);
+ UINT32 TextScaleFactor = 0;
+ UINT error = 0;
+
+ if (!context)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_textscaleinfo_order(s, &TextScaleFactor)))
+ return error;
+
+ if (context->custom)
+ {
+ IFCALLRET(context->ClientTextScale, error, context, TextScaleFactor);
+
+ if (error)
+ WLog_ERR(TAG, "context.ClientTextScale failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+static UINT rail_read_caretblinkinfo_order(wStream* s, UINT32* pCaretBlinkRate)
+{
+ WINPR_ASSERT(pCaretBlinkRate);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, *pCaretBlinkRate);
+ return CHANNEL_RC_OK;
+}
+
+static UINT rail_recv_caretblinkinfo_order(railPlugin* rail, wStream* s)
+{
+ RailClientContext* context = rail_get_client_interface(rail);
+ UINT32 CaretBlinkRate = 0;
+ UINT error = 0;
+
+ if (!context)
+ return ERROR_INVALID_PARAMETER;
+ if ((error = rail_read_caretblinkinfo_order(s, &CaretBlinkRate)))
+ return error;
+
+ if (context->custom)
+ {
+ IFCALLRET(context->ClientCaretBlinkRate, error, context, CaretBlinkRate);
+
+ if (error)
+ WLog_ERR(TAG, "context.ClientCaretBlinkRate failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_order_recv(LPVOID userdata, wStream* s)
+{
+ char buffer[128] = { 0 };
+ railPlugin* rail = userdata;
+ UINT16 orderType = 0;
+ UINT16 orderLength = 0;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!rail || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_pdu_header(s, &orderType, &orderLength)))
+ {
+ WLog_ERR(TAG, "rail_read_pdu_header failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ WLog_Print(rail->log, WLOG_DEBUG, "Received %s PDU, length:%" PRIu16 "",
+ rail_get_order_type_string_full(orderType, buffer, sizeof(buffer)), orderLength);
+
+ switch (orderType)
+ {
+ case TS_RAIL_ORDER_HANDSHAKE:
+ error = rail_recv_handshake_order(rail, s);
+ break;
+
+ case TS_RAIL_ORDER_COMPARTMENTINFO:
+ error = rail_recv_compartmentinfo_order(rail, s);
+ break;
+
+ case TS_RAIL_ORDER_HANDSHAKE_EX:
+ error = rail_recv_handshake_ex_order(rail, s);
+ break;
+
+ case TS_RAIL_ORDER_EXEC_RESULT:
+ error = rail_recv_exec_result_order(rail, s);
+ break;
+
+ case TS_RAIL_ORDER_SYSPARAM:
+ error = rail_recv_server_sysparam_order(rail, s);
+ break;
+
+ case TS_RAIL_ORDER_MINMAXINFO:
+ error = rail_recv_server_minmaxinfo_order(rail, s);
+ break;
+
+ case TS_RAIL_ORDER_LOCALMOVESIZE:
+ error = rail_recv_server_localmovesize_order(rail, s);
+ break;
+
+ case TS_RAIL_ORDER_GET_APPID_RESP:
+ error = rail_recv_server_get_appid_resp_order(rail, s);
+ break;
+
+ case TS_RAIL_ORDER_LANGBARINFO:
+ error = rail_recv_langbar_info_order(rail, s);
+ break;
+
+ case TS_RAIL_ORDER_TASKBARINFO:
+ error = rail_recv_taskbar_info_order(rail, s);
+ break;
+
+ case TS_RAIL_ORDER_ZORDER_SYNC:
+ error = rail_recv_zorder_sync_order(rail, s);
+ break;
+
+ case TS_RAIL_ORDER_CLOAK:
+ error = rail_recv_cloak_order(rail, s);
+ break;
+
+ case TS_RAIL_ORDER_POWER_DISPLAY_REQUEST:
+ error = rail_recv_power_display_request_order(rail, s);
+ break;
+
+ case TS_RAIL_ORDER_GET_APPID_RESP_EX:
+ error = rail_recv_get_application_id_extended_response_order(rail, s);
+ break;
+
+ case TS_RAIL_ORDER_TEXTSCALEINFO:
+ error = rail_recv_textscaleinfo_order(rail, s);
+ break;
+
+ case TS_RAIL_ORDER_CARETBLINKINFO:
+ error = rail_recv_caretblinkinfo_order(rail, s);
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unknown RAIL PDU %s received.",
+ rail_get_order_type_string_full(orderType, buffer, sizeof(buffer)));
+ return ERROR_INVALID_DATA;
+ }
+
+ if (error != CHANNEL_RC_OK)
+ {
+ char ebuffer[128] = { 0 };
+ WLog_Print(rail->log, WLOG_ERROR, "Failed to process rail %s PDU, length:%" PRIu16 "",
+ rail_get_order_type_string_full(orderType, ebuffer, sizeof(ebuffer)),
+ orderLength);
+ }
+
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_send_handshake_order(railPlugin* rail, const RAIL_HANDSHAKE_ORDER* handshake)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!rail || !handshake)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_HANDSHAKE_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rail_write_handshake_order(s, handshake);
+ error = rail_send_pdu(rail, s, TS_RAIL_ORDER_HANDSHAKE);
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_send_handshake_ex_order(railPlugin* rail, const RAIL_HANDSHAKE_EX_ORDER* handshakeEx)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!rail || !handshakeEx)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_HANDSHAKE_EX_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rail_write_handshake_ex_order(s, handshakeEx);
+ error = rail_send_pdu(rail, s, TS_RAIL_ORDER_HANDSHAKE_EX);
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_send_client_status_order(railPlugin* rail, const RAIL_CLIENT_STATUS_ORDER* clientStatus)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!rail || !clientStatus)
+ return ERROR_INVALID_PARAMETER;
+
+ rail->clientStatus = *clientStatus;
+ s = rail_pdu_init(RAIL_CLIENT_STATUS_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ error = rail_write_client_status_order(s, clientStatus);
+
+ if (error == ERROR_SUCCESS)
+ error = rail_send_pdu(rail, s, TS_RAIL_ORDER_CLIENTSTATUS);
+
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_send_client_exec_order(railPlugin* rail, UINT16 flags,
+ const RAIL_UNICODE_STRING* exeOrFile,
+ const RAIL_UNICODE_STRING* workingDir,
+ const RAIL_UNICODE_STRING* arguments)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+ size_t length = 0;
+
+ if (!rail || !exeOrFile || !workingDir || !arguments)
+ return ERROR_INVALID_PARAMETER;
+
+ length = RAIL_EXEC_ORDER_LENGTH + exeOrFile->length + workingDir->length + arguments->length;
+ s = rail_pdu_init(length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if ((error = rail_write_client_exec_order(s, flags, exeOrFile, workingDir, arguments)))
+ {
+ WLog_ERR(TAG, "rail_write_client_exec_order failed with error %" PRIu32 "!", error);
+ goto out;
+ }
+
+ if ((error = rail_send_pdu(rail, s, TS_RAIL_ORDER_EXEC)))
+ {
+ WLog_ERR(TAG, "rail_send_pdu failed with error %" PRIu32 "!", error);
+ goto out;
+ }
+
+out:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_send_client_activate_order(railPlugin* rail, const RAIL_ACTIVATE_ORDER* activate)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!rail || !activate)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_ACTIVATE_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ error = rail_write_client_activate_order(s, activate);
+
+ if (error == ERROR_SUCCESS)
+ error = rail_send_pdu(rail, s, TS_RAIL_ORDER_ACTIVATE);
+
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_send_client_sysmenu_order(railPlugin* rail, const RAIL_SYSMENU_ORDER* sysmenu)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!rail || !sysmenu)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_SYSMENU_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ error = rail_write_client_sysmenu_order(s, sysmenu);
+
+ if (error == ERROR_SUCCESS)
+ error = rail_send_pdu(rail, s, TS_RAIL_ORDER_SYSMENU);
+
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_send_client_syscommand_order(railPlugin* rail, const RAIL_SYSCOMMAND_ORDER* syscommand)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!rail || !syscommand)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_SYSCOMMAND_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ error = rail_write_client_syscommand_order(s, syscommand);
+
+ if (error == ERROR_SUCCESS)
+ error = rail_send_pdu(rail, s, TS_RAIL_ORDER_SYSCOMMAND);
+
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_send_client_notify_event_order(railPlugin* rail,
+ const RAIL_NOTIFY_EVENT_ORDER* notifyEvent)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!rail || !notifyEvent)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_NOTIFY_EVENT_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ error = rail_write_client_notify_event_order(s, notifyEvent);
+
+ if (ERROR_SUCCESS == error)
+ error = rail_send_pdu(rail, s, TS_RAIL_ORDER_NOTIFY_EVENT);
+
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_send_client_window_move_order(railPlugin* rail, const RAIL_WINDOW_MOVE_ORDER* windowMove)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!rail || !windowMove)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_WINDOW_MOVE_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ error = rail_write_client_window_move_order(s, windowMove);
+
+ if (error == ERROR_SUCCESS)
+ error = rail_send_pdu(rail, s, TS_RAIL_ORDER_WINDOWMOVE);
+
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_send_client_get_appid_req_order(railPlugin* rail,
+ const RAIL_GET_APPID_REQ_ORDER* getAppIdReq)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!rail || !getAppIdReq)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_GET_APPID_REQ_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ error = rail_write_client_get_appid_req_order(s, getAppIdReq);
+
+ if (error == ERROR_SUCCESS)
+ error = rail_send_pdu(rail, s, TS_RAIL_ORDER_GET_APPID_REQ);
+
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_send_client_langbar_info_order(railPlugin* rail,
+ const RAIL_LANGBAR_INFO_ORDER* langBarInfo)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!rail || !langBarInfo)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_DOCKED_LANGBAR_SUPPORTED))
+ return ERROR_BAD_CONFIGURATION;
+
+ s = rail_pdu_init(RAIL_LANGBAR_INFO_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ error = rail_write_langbar_info_order(s, langBarInfo);
+
+ if (ERROR_SUCCESS == error)
+ error = rail_send_pdu(rail, s, TS_RAIL_ORDER_LANGBARINFO);
+
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+UINT rail_send_client_languageime_info_order(railPlugin* rail,
+ const RAIL_LANGUAGEIME_INFO_ORDER* langImeInfo)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!rail || !langImeInfo)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED))
+ return ERROR_BAD_CONFIGURATION;
+
+ s = rail_pdu_init(RAIL_LANGUAGEIME_INFO_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ error = rail_write_languageime_info_order(s, langImeInfo);
+
+ if (ERROR_SUCCESS == error)
+ error = rail_send_pdu(rail, s, TS_RAIL_ORDER_LANGUAGEIMEINFO);
+
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+UINT rail_send_client_compartment_info_order(railPlugin* rail,
+ const RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!rail || !compartmentInfo)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED))
+ return ERROR_BAD_CONFIGURATION;
+
+ s = rail_pdu_init(RAIL_COMPARTMENT_INFO_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ error = rail_write_compartment_info_order(s, compartmentInfo);
+
+ if (ERROR_SUCCESS == error)
+ error = rail_send_pdu(rail, s, TS_RAIL_ORDER_COMPARTMENTINFO);
+
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+UINT rail_send_client_cloak_order(railPlugin* rail, const RAIL_CLOAK* cloak)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!rail || !cloak)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(5);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT32(s, cloak->windowId);
+ Stream_Write_UINT8(s, cloak->cloak ? 1 : 0);
+ error = rail_send_pdu(rail, s, TS_RAIL_ORDER_CLOAK);
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+UINT rail_send_client_snap_arrange_order(railPlugin* rail, const RAIL_SNAP_ARRANGE* snap)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!rail)
+ return ERROR_INVALID_PARAMETER;
+
+ /* 2.2.2.7.5 Client Window Snap PDU (TS_RAIL_ORDER_SNAP_ARRANGE) */
+ if ((rail->channelFlags & TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_SNAP_ARRANGE_SUPPORTED) == 0)
+ {
+ RAIL_WINDOW_MOVE_ORDER move = { 0 };
+ move.top = snap->top;
+ move.left = snap->left;
+ move.right = snap->right;
+ move.bottom = snap->bottom;
+ move.windowId = snap->windowId;
+ return rail_send_client_window_move_order(rail, &move);
+ }
+
+ s = rail_pdu_init(12);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT32(s, snap->windowId);
+ Stream_Write_INT16(s, snap->left);
+ Stream_Write_INT16(s, snap->top);
+ Stream_Write_INT16(s, snap->right);
+ Stream_Write_INT16(s, snap->bottom);
+ error = rail_send_pdu(rail, s, TS_RAIL_ORDER_SNAP_ARRANGE);
+ Stream_Free(s, TRUE);
+ return error;
+}
diff --git a/channels/rail/client/rail_orders.h b/channels/rail/client/rail_orders.h
new file mode 100644
index 0000000..1b15aae
--- /dev/null
+++ b/channels/rail/client/rail_orders.h
@@ -0,0 +1,60 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Remote Applications Integrated Locally (RAIL)
+ *
+ * Copyright 2009 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2011 Roman Barabanov <romanbarabanov@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RAIL_CLIENT_ORDERS_H
+#define FREERDP_CHANNEL_RAIL_CLIENT_ORDERS_H
+
+#include <freerdp/channels/log.h>
+
+#include "rail_main.h"
+
+#define TAG CHANNELS_TAG("rail.client")
+
+UINT rail_order_recv(LPVOID userdata, wStream* s);
+UINT rail_send_pdu(railPlugin* rail, wStream* s, UINT16 orderType);
+
+UINT rail_send_handshake_order(railPlugin* rail, const RAIL_HANDSHAKE_ORDER* handshake);
+UINT rail_send_handshake_ex_order(railPlugin* rail, const RAIL_HANDSHAKE_EX_ORDER* handshakeEx);
+UINT rail_send_client_status_order(railPlugin* rail, const RAIL_CLIENT_STATUS_ORDER* clientStatus);
+UINT rail_send_client_exec_order(railPlugin* rail, UINT16 flags,
+ const RAIL_UNICODE_STRING* exeOrFile,
+ const RAIL_UNICODE_STRING* workingDir,
+ const RAIL_UNICODE_STRING* arguments);
+UINT rail_send_client_activate_order(railPlugin* rail, const RAIL_ACTIVATE_ORDER* activate);
+UINT rail_send_client_sysmenu_order(railPlugin* rail, const RAIL_SYSMENU_ORDER* sysmenu);
+UINT rail_send_client_syscommand_order(railPlugin* rail, const RAIL_SYSCOMMAND_ORDER* syscommand);
+
+UINT rail_send_client_notify_event_order(railPlugin* rail,
+ const RAIL_NOTIFY_EVENT_ORDER* notifyEvent);
+UINT rail_send_client_window_move_order(railPlugin* rail, const RAIL_WINDOW_MOVE_ORDER* windowMove);
+UINT rail_send_client_get_appid_req_order(railPlugin* rail,
+ const RAIL_GET_APPID_REQ_ORDER* getAppIdReq);
+UINT rail_send_client_langbar_info_order(railPlugin* rail,
+ const RAIL_LANGBAR_INFO_ORDER* langBarInfo);
+UINT rail_send_client_languageime_info_order(railPlugin* rail,
+ const RAIL_LANGUAGEIME_INFO_ORDER* langImeInfo);
+UINT rail_send_client_cloak_order(railPlugin* rail, const RAIL_CLOAK* cloak);
+UINT rail_send_client_snap_arrange_order(railPlugin* rail, const RAIL_SNAP_ARRANGE* snap);
+UINT rail_send_client_compartment_info_order(railPlugin* rail,
+ const RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo);
+
+#endif /* FREERDP_CHANNEL_RAIL_CLIENT_ORDERS_H */
diff --git a/channels/rail/rail_common.c b/channels/rail/rail_common.c
new file mode 100644
index 0000000..fb6bc80
--- /dev/null
+++ b/channels/rail/rail_common.c
@@ -0,0 +1,591 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RAIL common functions
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2011 Roman Barabanov <romanbarabanov@gmail.com>
+ * Copyright 2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "rail_common.h"
+
+#include <winpr/crt.h>
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("rail.common")
+
+const char* rail_get_order_type_string(UINT16 orderType)
+{
+ switch (orderType)
+ {
+ case TS_RAIL_ORDER_EXEC:
+ return "TS_RAIL_ORDER_EXEC";
+ case TS_RAIL_ORDER_ACTIVATE:
+ return "TS_RAIL_ORDER_ACTIVATE";
+ case TS_RAIL_ORDER_SYSPARAM:
+ return "TS_RAIL_ORDER_SYSPARAM";
+ case TS_RAIL_ORDER_SYSCOMMAND:
+ return "TS_RAIL_ORDER_SYSCOMMAND";
+ case TS_RAIL_ORDER_HANDSHAKE:
+ return "TS_RAIL_ORDER_HANDSHAKE";
+ case TS_RAIL_ORDER_NOTIFY_EVENT:
+ return "TS_RAIL_ORDER_NOTIFY_EVENT";
+ case TS_RAIL_ORDER_WINDOWMOVE:
+ return "TS_RAIL_ORDER_WINDOWMOVE";
+ case TS_RAIL_ORDER_LOCALMOVESIZE:
+ return "TS_RAIL_ORDER_LOCALMOVESIZE";
+ case TS_RAIL_ORDER_MINMAXINFO:
+ return "TS_RAIL_ORDER_MINMAXINFO";
+ case TS_RAIL_ORDER_CLIENTSTATUS:
+ return "TS_RAIL_ORDER_CLIENTSTATUS";
+ case TS_RAIL_ORDER_SYSMENU:
+ return "TS_RAIL_ORDER_SYSMENU";
+ case TS_RAIL_ORDER_LANGBARINFO:
+ return "TS_RAIL_ORDER_LANGBARINFO";
+ case TS_RAIL_ORDER_GET_APPID_REQ:
+ return "TS_RAIL_ORDER_GET_APPID_REQ";
+ case TS_RAIL_ORDER_GET_APPID_RESP:
+ return "TS_RAIL_ORDER_GET_APPID_RESP";
+ case TS_RAIL_ORDER_TASKBARINFO:
+ return "TS_RAIL_ORDER_TASKBARINFO";
+ case TS_RAIL_ORDER_LANGUAGEIMEINFO:
+ return "TS_RAIL_ORDER_LANGUAGEIMEINFO";
+ case TS_RAIL_ORDER_COMPARTMENTINFO:
+ return "TS_RAIL_ORDER_COMPARTMENTINFO";
+ case TS_RAIL_ORDER_HANDSHAKE_EX:
+ return "TS_RAIL_ORDER_HANDSHAKE_EX";
+ case TS_RAIL_ORDER_ZORDER_SYNC:
+ return "TS_RAIL_ORDER_ZORDER_SYNC";
+ case TS_RAIL_ORDER_CLOAK:
+ return "TS_RAIL_ORDER_CLOAK";
+ case TS_RAIL_ORDER_POWER_DISPLAY_REQUEST:
+ return "TS_RAIL_ORDER_POWER_DISPLAY_REQUEST";
+ case TS_RAIL_ORDER_SNAP_ARRANGE:
+ return "TS_RAIL_ORDER_SNAP_ARRANGE";
+ case TS_RAIL_ORDER_GET_APPID_RESP_EX:
+ return "TS_RAIL_ORDER_GET_APPID_RESP_EX";
+ case TS_RAIL_ORDER_EXEC_RESULT:
+ return "TS_RAIL_ORDER_EXEC_RESULT";
+ case TS_RAIL_ORDER_TEXTSCALEINFO:
+ return "TS_RAIL_ORDER_TEXTSCALEINFO";
+ case TS_RAIL_ORDER_CARETBLINKINFO:
+ return "TS_RAIL_ORDER_CARETBLINKINFO";
+ default:
+ return "TS_RAIL_ORDER_UNKNOWN";
+ }
+}
+
+const char* rail_get_order_type_string_full(UINT16 orderType, char* buffer, size_t length)
+{
+ _snprintf(buffer, length, "%s[0x%04" PRIx16 "]", rail_get_order_type_string(orderType),
+ orderType);
+ return buffer;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_read_pdu_header(wStream* s, UINT16* orderType, UINT16* orderLength)
+{
+ if (!s || !orderType || !orderLength)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, *orderType); /* orderType (2 bytes) */
+ Stream_Read_UINT16(s, *orderLength); /* orderLength (2 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+void rail_write_pdu_header(wStream* s, UINT16 orderType, UINT16 orderLength)
+{
+ Stream_Write_UINT16(s, orderType); /* orderType (2 bytes) */
+ Stream_Write_UINT16(s, orderLength); /* orderLength (2 bytes) */
+}
+
+wStream* rail_pdu_init(size_t length)
+{
+ wStream* s = NULL;
+ s = Stream_New(NULL, length + RAIL_PDU_HEADER_LENGTH);
+
+ if (!s)
+ return NULL;
+
+ Stream_Seek(s, RAIL_PDU_HEADER_LENGTH);
+ return s;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_read_handshake_order(wStream* s, RAIL_HANDSHAKE_ORDER* handshake)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, handshake->buildNumber); /* buildNumber (4 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+void rail_write_handshake_order(wStream* s, const RAIL_HANDSHAKE_ORDER* handshake)
+{
+ Stream_Write_UINT32(s, handshake->buildNumber); /* buildNumber (4 bytes) */
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_read_handshake_ex_order(wStream* s, RAIL_HANDSHAKE_EX_ORDER* handshakeEx)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, handshakeEx->buildNumber); /* buildNumber (4 bytes) */
+ Stream_Read_UINT32(s, handshakeEx->railHandshakeFlags); /* railHandshakeFlags (4 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+void rail_write_handshake_ex_order(wStream* s, const RAIL_HANDSHAKE_EX_ORDER* handshakeEx)
+{
+ Stream_Write_UINT32(s, handshakeEx->buildNumber); /* buildNumber (4 bytes) */
+ Stream_Write_UINT32(s, handshakeEx->railHandshakeFlags); /* railHandshakeFlags (4 bytes) */
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_write_unicode_string(wStream* s, const RAIL_UNICODE_STRING* unicode_string)
+{
+ if (!s || !unicode_string)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_EnsureRemainingCapacity(s, 2 + unicode_string->length))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, unicode_string->length); /* cbString (2 bytes) */
+ Stream_Write(s, unicode_string->string, unicode_string->length); /* string */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_write_unicode_string_value(wStream* s, const RAIL_UNICODE_STRING* unicode_string)
+{
+ size_t length = 0;
+
+ if (!s || !unicode_string)
+ return ERROR_INVALID_PARAMETER;
+
+ length = unicode_string->length;
+
+ if (length > 0)
+ {
+ if (!Stream_EnsureRemainingCapacity(s, length))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write(s, unicode_string->string, length); /* string */
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_high_contrast(wStream* s, RAIL_HIGH_CONTRAST* highContrast)
+{
+ if (!s || !highContrast)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, highContrast->flags); /* flags (4 bytes) */
+ Stream_Read_UINT32(s, highContrast->colorSchemeLength); /* colorSchemeLength (4 bytes) */
+
+ if (!rail_read_unicode_string(s, &highContrast->colorScheme)) /* colorScheme */
+ return ERROR_INTERNAL_ERROR;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_write_high_contrast(wStream* s, const RAIL_HIGH_CONTRAST* highContrast)
+{
+ UINT32 colorSchemeLength = 0;
+
+ if (!s || !highContrast)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return CHANNEL_RC_NO_MEMORY;
+
+ colorSchemeLength = highContrast->colorScheme.length + 2;
+ Stream_Write_UINT32(s, highContrast->flags); /* flags (4 bytes) */
+ Stream_Write_UINT32(s, colorSchemeLength); /* colorSchemeLength (4 bytes) */
+ return rail_write_unicode_string(s, &highContrast->colorScheme); /* colorScheme */
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_filterkeys(wStream* s, TS_FILTERKEYS* filterKeys)
+{
+ if (!s || !filterKeys)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 20))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, filterKeys->Flags);
+ Stream_Read_UINT32(s, filterKeys->WaitTime);
+ Stream_Read_UINT32(s, filterKeys->DelayTime);
+ Stream_Read_UINT32(s, filterKeys->RepeatTime);
+ Stream_Read_UINT32(s, filterKeys->BounceTime);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_write_filterkeys(wStream* s, const TS_FILTERKEYS* filterKeys)
+{
+ if (!s || !filterKeys)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_EnsureRemainingCapacity(s, 20))
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT32(s, filterKeys->Flags);
+ Stream_Write_UINT32(s, filterKeys->WaitTime);
+ Stream_Write_UINT32(s, filterKeys->DelayTime);
+ Stream_Write_UINT32(s, filterKeys->RepeatTime);
+ Stream_Write_UINT32(s, filterKeys->BounceTime);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rail_read_sysparam_order(wStream* s, RAIL_SYSPARAM_ORDER* sysparam, BOOL extendedSpiSupported)
+{
+ BYTE body = 0;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!s || !sysparam)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 5))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, sysparam->param); /* systemParam (4 bytes) */
+
+ sysparam->params = 0; /* bitflags of received params */
+
+ switch (sysparam->param)
+ {
+ /* Client sysparams */
+ case SPI_SET_DRAG_FULL_WINDOWS:
+ sysparam->params |= SPI_MASK_SET_DRAG_FULL_WINDOWS;
+ Stream_Read_UINT8(s, body); /* body (1 byte) */
+ sysparam->dragFullWindows = body != 0;
+ break;
+
+ case SPI_SET_KEYBOARD_CUES:
+ sysparam->params |= SPI_MASK_SET_KEYBOARD_CUES;
+ Stream_Read_UINT8(s, body); /* body (1 byte) */
+ sysparam->keyboardCues = body != 0;
+ break;
+
+ case SPI_SET_KEYBOARD_PREF:
+ sysparam->params |= SPI_MASK_SET_KEYBOARD_PREF;
+ Stream_Read_UINT8(s, body); /* body (1 byte) */
+ sysparam->keyboardPref = body != 0;
+ break;
+
+ case SPI_SET_MOUSE_BUTTON_SWAP:
+ sysparam->params |= SPI_MASK_SET_MOUSE_BUTTON_SWAP;
+ Stream_Read_UINT8(s, body); /* body (1 byte) */
+ sysparam->mouseButtonSwap = body != 0;
+ break;
+
+ case SPI_SET_WORK_AREA:
+ sysparam->params |= SPI_MASK_SET_WORK_AREA;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, sysparam->workArea.left); /* left (2 bytes) */
+ Stream_Read_UINT16(s, sysparam->workArea.top); /* top (2 bytes) */
+ Stream_Read_UINT16(s, sysparam->workArea.right); /* right (2 bytes) */
+ Stream_Read_UINT16(s, sysparam->workArea.bottom); /* bottom (2 bytes) */
+ break;
+
+ case SPI_DISPLAY_CHANGE:
+ sysparam->params |= SPI_MASK_DISPLAY_CHANGE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, sysparam->displayChange.left); /* left (2 bytes) */
+ Stream_Read_UINT16(s, sysparam->displayChange.top); /* top (2 bytes) */
+ Stream_Read_UINT16(s, sysparam->displayChange.right); /* right (2 bytes) */
+ Stream_Read_UINT16(s, sysparam->displayChange.bottom); /* bottom (2 bytes) */
+ break;
+
+ case SPI_TASKBAR_POS:
+ sysparam->params |= SPI_MASK_TASKBAR_POS;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, sysparam->taskbarPos.left); /* left (2 bytes) */
+ Stream_Read_UINT16(s, sysparam->taskbarPos.top); /* top (2 bytes) */
+ Stream_Read_UINT16(s, sysparam->taskbarPos.right); /* right (2 bytes) */
+ Stream_Read_UINT16(s, sysparam->taskbarPos.bottom); /* bottom (2 bytes) */
+ break;
+
+ case SPI_SET_HIGH_CONTRAST:
+ sysparam->params |= SPI_MASK_SET_HIGH_CONTRAST;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ error = rail_read_high_contrast(s, &sysparam->highContrast);
+ break;
+
+ case SPI_SETCARETWIDTH:
+ sysparam->params |= SPI_MASK_SET_CARET_WIDTH;
+
+ if (!extendedSpiSupported)
+ return ERROR_INVALID_DATA;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, sysparam->caretWidth);
+
+ if (sysparam->caretWidth < 0x0001)
+ return ERROR_INVALID_DATA;
+
+ break;
+
+ case SPI_SETSTICKYKEYS:
+ sysparam->params |= SPI_MASK_SET_STICKY_KEYS;
+
+ if (!extendedSpiSupported)
+ return ERROR_INVALID_DATA;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, sysparam->stickyKeys);
+ break;
+
+ case SPI_SETTOGGLEKEYS:
+ sysparam->params |= SPI_MASK_SET_TOGGLE_KEYS;
+
+ if (!extendedSpiSupported)
+ return ERROR_INVALID_DATA;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, sysparam->toggleKeys);
+ break;
+
+ case SPI_SETFILTERKEYS:
+ sysparam->params |= SPI_MASK_SET_FILTER_KEYS;
+
+ if (!extendedSpiSupported)
+ return ERROR_INVALID_DATA;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 20))
+ return ERROR_INVALID_DATA;
+
+ error = rail_read_filterkeys(s, &sysparam->filterKeys);
+ break;
+
+ /* Server sysparams */
+ case SPI_SETSCREENSAVEACTIVE:
+ sysparam->params |= SPI_MASK_SET_SCREEN_SAVE_ACTIVE;
+
+ Stream_Read_UINT8(s, body); /* body (1 byte) */
+ sysparam->setScreenSaveActive = body != 0;
+ break;
+
+ case SPI_SETSCREENSAVESECURE:
+ sysparam->params |= SPI_MASK_SET_SET_SCREEN_SAVE_SECURE;
+
+ Stream_Read_UINT8(s, body); /* body (1 byte) */
+ sysparam->setScreenSaveSecure = body != 0;
+ break;
+
+ default:
+ break;
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 err2or code
+ */
+UINT rail_write_sysparam_order(wStream* s, const RAIL_SYSPARAM_ORDER* sysparam,
+ BOOL extendedSpiSupported)
+{
+ BYTE body = 0;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!s || !sysparam)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_EnsureRemainingCapacity(s, 12))
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT32(s, sysparam->param); /* systemParam (4 bytes) */
+
+ switch (sysparam->param)
+ {
+ /* Client sysparams */
+ case SPI_SET_DRAG_FULL_WINDOWS:
+ body = sysparam->dragFullWindows ? 1 : 0;
+ Stream_Write_UINT8(s, body);
+ break;
+
+ case SPI_SET_KEYBOARD_CUES:
+ body = sysparam->keyboardCues ? 1 : 0;
+ Stream_Write_UINT8(s, body);
+ break;
+
+ case SPI_SET_KEYBOARD_PREF:
+ body = sysparam->keyboardPref ? 1 : 0;
+ Stream_Write_UINT8(s, body);
+ break;
+
+ case SPI_SET_MOUSE_BUTTON_SWAP:
+ body = sysparam->mouseButtonSwap ? 1 : 0;
+ Stream_Write_UINT8(s, body);
+ break;
+
+ case SPI_SET_WORK_AREA:
+ Stream_Write_UINT16(s, sysparam->workArea.left); /* left (2 bytes) */
+ Stream_Write_UINT16(s, sysparam->workArea.top); /* top (2 bytes) */
+ Stream_Write_UINT16(s, sysparam->workArea.right); /* right (2 bytes) */
+ Stream_Write_UINT16(s, sysparam->workArea.bottom); /* bottom (2 bytes) */
+ break;
+
+ case SPI_DISPLAY_CHANGE:
+ Stream_Write_UINT16(s, sysparam->displayChange.left); /* left (2 bytes) */
+ Stream_Write_UINT16(s, sysparam->displayChange.top); /* top (2 bytes) */
+ Stream_Write_UINT16(s, sysparam->displayChange.right); /* right (2 bytes) */
+ Stream_Write_UINT16(s, sysparam->displayChange.bottom); /* bottom (2 bytes) */
+ break;
+
+ case SPI_TASKBAR_POS:
+ Stream_Write_UINT16(s, sysparam->taskbarPos.left); /* left (2 bytes) */
+ Stream_Write_UINT16(s, sysparam->taskbarPos.top); /* top (2 bytes) */
+ Stream_Write_UINT16(s, sysparam->taskbarPos.right); /* right (2 bytes) */
+ Stream_Write_UINT16(s, sysparam->taskbarPos.bottom); /* bottom (2 bytes) */
+ break;
+
+ case SPI_SET_HIGH_CONTRAST:
+ error = rail_write_high_contrast(s, &sysparam->highContrast);
+ break;
+
+ case SPI_SETCARETWIDTH:
+ if (!extendedSpiSupported)
+ return ERROR_INVALID_DATA;
+
+ if (sysparam->caretWidth < 0x0001)
+ return ERROR_INVALID_DATA;
+
+ Stream_Write_UINT32(s, sysparam->caretWidth);
+ break;
+
+ case SPI_SETSTICKYKEYS:
+ if (!extendedSpiSupported)
+ return ERROR_INVALID_DATA;
+
+ Stream_Write_UINT32(s, sysparam->stickyKeys);
+ break;
+
+ case SPI_SETTOGGLEKEYS:
+ if (!extendedSpiSupported)
+ return ERROR_INVALID_DATA;
+
+ Stream_Write_UINT32(s, sysparam->toggleKeys);
+ break;
+
+ case SPI_SETFILTERKEYS:
+ if (!extendedSpiSupported)
+ return ERROR_INVALID_DATA;
+
+ error = rail_write_filterkeys(s, &sysparam->filterKeys);
+ break;
+
+ /* Server sysparams */
+ case SPI_SETSCREENSAVEACTIVE:
+ body = sysparam->setScreenSaveActive ? 1 : 0;
+ Stream_Write_UINT8(s, body);
+ break;
+
+ case SPI_SETSCREENSAVESECURE:
+ body = sysparam->setScreenSaveSecure ? 1 : 0;
+ Stream_Write_UINT8(s, body);
+ break;
+
+ default:
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ return error;
+}
+
+BOOL rail_is_extended_spi_supported(UINT32 channelFlags)
+{
+ return (channelFlags & TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_EXTENDED_SPI_SUPPORTED) ? TRUE : FALSE;
+}
diff --git a/channels/rail/rail_common.h b/channels/rail/rail_common.h
new file mode 100644
index 0000000..34b6fa0
--- /dev/null
+++ b/channels/rail/rail_common.h
@@ -0,0 +1,76 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RAIL Virtual Channel Plugin
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2011 Roman Barabanov <romanbarabanov@gmail.com>
+ * Copyright 2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RAIL_COMMON_H
+#define FREERDP_CHANNEL_RAIL_COMMON_H
+
+#include <freerdp/rail.h>
+
+#define RAIL_PDU_HEADER_LENGTH 4
+
+/* Fixed length of PDUs, excluding variable lengths */
+#define RAIL_HANDSHAKE_ORDER_LENGTH 4 /* fixed */
+#define RAIL_HANDSHAKE_EX_ORDER_LENGTH 8 /* fixed */
+#define RAIL_CLIENT_STATUS_ORDER_LENGTH 4 /* fixed */
+#define RAIL_EXEC_ORDER_LENGTH 8 /* variable */
+#define RAIL_EXEC_RESULT_ORDER_LENGTH 12 /* variable */
+#define RAIL_SYSPARAM_ORDER_LENGTH 4 /* variable */
+#define RAIL_MINMAXINFO_ORDER_LENGTH 20 /* fixed */
+#define RAIL_LOCALMOVESIZE_ORDER_LENGTH 12 /* fixed */
+#define RAIL_ACTIVATE_ORDER_LENGTH 5 /* fixed */
+#define RAIL_SYSMENU_ORDER_LENGTH 8 /* fixed */
+#define RAIL_SYSCOMMAND_ORDER_LENGTH 6 /* fixed */
+#define RAIL_NOTIFY_EVENT_ORDER_LENGTH 12 /* fixed */
+#define RAIL_WINDOW_MOVE_ORDER_LENGTH 12 /* fixed */
+#define RAIL_SNAP_ARRANGE_ORDER_LENGTH 12 /* fixed */
+#define RAIL_GET_APPID_REQ_ORDER_LENGTH 4 /* fixed */
+#define RAIL_LANGBAR_INFO_ORDER_LENGTH 4 /* fixed */
+#define RAIL_LANGUAGEIME_INFO_ORDER_LENGTH 42 /* fixed */
+#define RAIL_COMPARTMENT_INFO_ORDER_LENGTH 16 /* fixed */
+#define RAIL_CLOAK_ORDER_LENGTH 5 /* fixed */
+#define RAIL_TASKBAR_INFO_ORDER_LENGTH 12 /* fixed */
+#define RAIL_Z_ORDER_SYNC_ORDER_LENGTH 4 /* fixed */
+#define RAIL_POWER_DISPLAY_REQUEST_ORDER_LENGTH 4 /* fixed */
+#define RAIL_GET_APPID_RESP_ORDER_LENGTH 524 /* fixed */
+#define RAIL_GET_APPID_RESP_EX_ORDER_LENGTH 1048 /* fixed */
+
+UINT rail_read_handshake_order(wStream* s, RAIL_HANDSHAKE_ORDER* handshake);
+void rail_write_handshake_order(wStream* s, const RAIL_HANDSHAKE_ORDER* handshake);
+UINT rail_read_handshake_ex_order(wStream* s, RAIL_HANDSHAKE_EX_ORDER* handshakeEx);
+void rail_write_handshake_ex_order(wStream* s, const RAIL_HANDSHAKE_EX_ORDER* handshakeEx);
+
+wStream* rail_pdu_init(size_t length);
+UINT rail_read_pdu_header(wStream* s, UINT16* orderType, UINT16* orderLength);
+void rail_write_pdu_header(wStream* s, UINT16 orderType, UINT16 orderLength);
+
+UINT rail_write_unicode_string(wStream* s, const RAIL_UNICODE_STRING* unicode_string);
+UINT rail_write_unicode_string_value(wStream* s, const RAIL_UNICODE_STRING* unicode_string);
+
+UINT rail_read_sysparam_order(wStream* s, RAIL_SYSPARAM_ORDER* sysparam, BOOL extendedSpiSupported);
+UINT rail_write_sysparam_order(wStream* s, const RAIL_SYSPARAM_ORDER* sysparam,
+ BOOL extendedSpiSupported);
+BOOL rail_is_extended_spi_supported(UINT32 channelsFlags);
+const char* rail_get_order_type_string(UINT16 orderType);
+const char* rail_get_order_type_string_full(UINT16 orderType, char* buffer, size_t length);
+
+#endif /* FREERDP_CHANNEL_RAIL_COMMON_H */
diff --git a/channels/rail/server/CMakeLists.txt b/channels/rail/server/CMakeLists.txt
new file mode 100644
index 0000000..f8f64be
--- /dev/null
+++ b/channels/rail/server/CMakeLists.txt
@@ -0,0 +1,33 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_server("rail")
+
+set(${MODULE_PREFIX}_SRCS
+ ../rail_common.c
+ ../rail_common.h
+ rail_main.c
+ rail_main.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+
+include_directories(..)
+
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx")
diff --git a/channels/rail/server/rail_main.c b/channels/rail/server/rail_main.c
new file mode 100644
index 0000000..8e38c2b
--- /dev/null
+++ b/channels/rail/server/rail_main.c
@@ -0,0 +1,1728 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RAIL Virtual Channel Plugin
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ *
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/types.h>
+#include <freerdp/constants.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/log.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+
+#include "rail_main.h"
+
+#define TAG CHANNELS_TAG("rail.server")
+
+/**
+ * Sends a single rail PDU on the channel
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_send(RailServerContext* context, wStream* s, ULONG length)
+{
+ UINT status = CHANNEL_RC_OK;
+ ULONG written = 0;
+
+ if (!context)
+ return CHANNEL_RC_BAD_INIT_HANDLE;
+
+ if (!WTSVirtualChannelWrite(context->priv->rail_channel, (PCHAR)Stream_Buffer(s), length,
+ &written))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
+ status = ERROR_INTERNAL_ERROR;
+ }
+
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_server_send_pdu(RailServerContext* context, wStream* s, UINT16 orderType)
+{
+ char buffer[128] = { 0 };
+ UINT16 orderLength = 0;
+
+ if (!context || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ orderLength = (UINT16)Stream_GetPosition(s);
+ Stream_SetPosition(s, 0);
+ rail_write_pdu_header(s, orderType, orderLength);
+ Stream_SetPosition(s, orderLength);
+ WLog_DBG(TAG, "Sending %s PDU, length: %" PRIu16 "",
+ rail_get_order_type_string_full(orderType, buffer, sizeof(buffer)), orderLength);
+ return rail_send(context, s, orderLength);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_write_local_move_size_order(wStream* s,
+ const RAIL_LOCALMOVESIZE_ORDER* localMoveSize)
+{
+ if (!s || !localMoveSize)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, localMoveSize->windowId); /* WindowId (4 bytes) */
+ Stream_Write_UINT16(s, localMoveSize->isMoveSizeStart ? 1 : 0); /* IsMoveSizeStart (2 bytes) */
+ Stream_Write_UINT16(s, localMoveSize->moveSizeType); /* MoveSizeType (2 bytes) */
+ Stream_Write_UINT16(s, localMoveSize->posX); /* PosX (2 bytes) */
+ Stream_Write_UINT16(s, localMoveSize->posY); /* PosY (2 bytes) */
+ return ERROR_SUCCESS;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_write_min_max_info_order(wStream* s, const RAIL_MINMAXINFO_ORDER* minMaxInfo)
+{
+ if (!s || !minMaxInfo)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, minMaxInfo->windowId); /* WindowId (4 bytes) */
+ Stream_Write_INT16(s, minMaxInfo->maxWidth); /* MaxWidth (2 bytes) */
+ Stream_Write_INT16(s, minMaxInfo->maxHeight); /* MaxHeight (2 bytes) */
+ Stream_Write_INT16(s, minMaxInfo->maxPosX); /* MaxPosX (2 bytes) */
+ Stream_Write_INT16(s, minMaxInfo->maxPosY); /* MaxPosY (2 bytes) */
+ Stream_Write_INT16(s, minMaxInfo->minTrackWidth); /* MinTrackWidth (2 bytes) */
+ Stream_Write_INT16(s, minMaxInfo->minTrackHeight); /* MinTrackHeight (2 bytes) */
+ Stream_Write_INT16(s, minMaxInfo->maxTrackWidth); /* MaxTrackWidth (2 bytes) */
+ Stream_Write_INT16(s, minMaxInfo->maxTrackHeight); /* MaxTrackHeight (2 bytes) */
+ return ERROR_SUCCESS;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_write_taskbar_info_order(wStream* s, const RAIL_TASKBAR_INFO_ORDER* taskbarInfo)
+{
+ if (!s || !taskbarInfo)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, taskbarInfo->TaskbarMessage); /* TaskbarMessage (4 bytes) */
+ Stream_Write_UINT32(s, taskbarInfo->WindowIdTab); /* WindowIdTab (4 bytes) */
+ Stream_Write_UINT32(s, taskbarInfo->Body); /* Body (4 bytes) */
+ return ERROR_SUCCESS;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_write_langbar_info_order(wStream* s, const RAIL_LANGBAR_INFO_ORDER* langbarInfo)
+{
+ if (!s || !langbarInfo)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, langbarInfo->languageBarStatus); /* LanguageBarStatus (4 bytes) */
+ return ERROR_SUCCESS;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_write_exec_result_order(wStream* s, const RAIL_EXEC_RESULT_ORDER* execResult)
+{
+ if (!s || !execResult)
+ return ERROR_INVALID_PARAMETER;
+
+ if (execResult->exeOrFile.length > 520 || execResult->exeOrFile.length < 1)
+ return ERROR_INVALID_DATA;
+
+ Stream_Write_UINT16(s, execResult->flags); /* Flags (2 bytes) */
+ Stream_Write_UINT16(s, execResult->execResult); /* ExecResult (2 bytes) */
+ Stream_Write_UINT32(s, execResult->rawResult); /* RawResult (4 bytes) */
+ Stream_Write_UINT16(s, 0); /* Padding (2 bytes) */
+ Stream_Write_UINT16(s, execResult->exeOrFile.length); /* ExeOrFileLength (2 bytes) */
+ Stream_Write(s, execResult->exeOrFile.string,
+ execResult->exeOrFile.length); /* ExeOrFile (variable) */
+ return ERROR_SUCCESS;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_write_z_order_sync_order(wStream* s, const RAIL_ZORDER_SYNC* zOrderSync)
+{
+ if (!s || !zOrderSync)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, zOrderSync->windowIdMarker); /* WindowIdMarker (4 bytes) */
+ return ERROR_SUCCESS;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_write_cloak_order(wStream* s, const RAIL_CLOAK* cloak)
+{
+ if (!s || !cloak)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, cloak->windowId); /* WindowId (4 bytes) */
+ Stream_Write_UINT8(s, cloak->cloak ? 1 : 0); /* Cloaked (1 byte) */
+ return ERROR_SUCCESS;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+rail_write_power_display_request_order(wStream* s,
+ const RAIL_POWER_DISPLAY_REQUEST* powerDisplayRequest)
+{
+ if (!s || !powerDisplayRequest)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, powerDisplayRequest->active ? 1 : 0); /* Active (4 bytes) */
+ return ERROR_SUCCESS;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_write_get_app_id_resp_order(wStream* s,
+ const RAIL_GET_APPID_RESP_ORDER* getAppidResp)
+{
+ if (!s || !getAppidResp)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, getAppidResp->windowId); /* WindowId (4 bytes) */
+ Stream_Write_UTF16_String(
+ s, getAppidResp->applicationId,
+ ARRAYSIZE(getAppidResp->applicationId)); /* ApplicationId (512 bytes) */
+ return ERROR_SUCCESS;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_write_get_appid_resp_ex_order(wStream* s,
+ const RAIL_GET_APPID_RESP_EX* getAppidRespEx)
+{
+ if (!s || !getAppidRespEx)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(s, getAppidRespEx->windowID); /* WindowId (4 bytes) */
+ Stream_Write_UTF16_String(
+ s, getAppidRespEx->applicationID,
+ ARRAYSIZE(getAppidRespEx->applicationID)); /* ApplicationId (520 bytes) */
+ Stream_Write_UINT32(s, getAppidRespEx->processId); /* ProcessId (4 bytes) */
+ Stream_Write_UTF16_String(
+ s, getAppidRespEx->processImageName,
+ ARRAYSIZE(getAppidRespEx->processImageName)); /* ProcessImageName (520 bytes) */
+ return ERROR_SUCCESS;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_send_server_handshake(RailServerContext* context,
+ const RAIL_HANDSHAKE_ORDER* handshake)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!context || !handshake)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_HANDSHAKE_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rail_write_handshake_order(s, handshake);
+ error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_HANDSHAKE);
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_send_server_handshake_ex(RailServerContext* context,
+ const RAIL_HANDSHAKE_EX_ORDER* handshakeEx)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!context || !handshakeEx || !context->priv)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_HANDSHAKE_EX_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rail_server_set_handshake_ex_flags(context, handshakeEx->railHandshakeFlags);
+
+ rail_write_handshake_ex_order(s, handshakeEx);
+ error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_HANDSHAKE_EX);
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_send_server_sysparam(RailServerContext* context,
+ const RAIL_SYSPARAM_ORDER* sysparam)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+ RailServerPrivate* priv = NULL;
+ BOOL extendedSpiSupported = 0;
+
+ if (!context || !sysparam)
+ return ERROR_INVALID_PARAMETER;
+
+ priv = context->priv;
+
+ if (!priv)
+ return ERROR_INVALID_PARAMETER;
+
+ extendedSpiSupported = rail_is_extended_spi_supported(context->priv->channelFlags);
+ s = rail_pdu_init(RAIL_SYSPARAM_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rail_write_sysparam_order(s, sysparam, extendedSpiSupported);
+ error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_SYSPARAM);
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_send_server_local_move_size(RailServerContext* context,
+ const RAIL_LOCALMOVESIZE_ORDER* localMoveSize)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!context || !localMoveSize)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_LOCALMOVESIZE_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rail_write_local_move_size_order(s, localMoveSize);
+ error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_LOCALMOVESIZE);
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_send_server_min_max_info(RailServerContext* context,
+ const RAIL_MINMAXINFO_ORDER* minMaxInfo)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!context || !minMaxInfo)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_MINMAXINFO_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rail_write_min_max_info_order(s, minMaxInfo);
+ error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_MINMAXINFO);
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_send_server_taskbar_info(RailServerContext* context,
+ const RAIL_TASKBAR_INFO_ORDER* taskbarInfo)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!context || !taskbarInfo)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_TASKBAR_INFO_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rail_write_taskbar_info_order(s, taskbarInfo);
+ error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_TASKBARINFO);
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_send_server_langbar_info(RailServerContext* context,
+ const RAIL_LANGBAR_INFO_ORDER* langbarInfo)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!context || !langbarInfo)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_LANGBAR_INFO_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rail_write_langbar_info_order(s, langbarInfo);
+ error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_LANGBARINFO);
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_send_server_exec_result(RailServerContext* context,
+ const RAIL_EXEC_RESULT_ORDER* execResult)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!context || !execResult)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_EXEC_RESULT_ORDER_LENGTH + execResult->exeOrFile.length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rail_write_exec_result_order(s, execResult);
+ error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_EXEC_RESULT);
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_send_server_z_order_sync(RailServerContext* context,
+ const RAIL_ZORDER_SYNC* zOrderSync)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!context || !zOrderSync)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_Z_ORDER_SYNC_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rail_write_z_order_sync_order(s, zOrderSync);
+ error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_ZORDER_SYNC);
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_send_server_cloak(RailServerContext* context, const RAIL_CLOAK* cloak)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!context || !cloak)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_CLOAK_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rail_write_cloak_order(s, cloak);
+ error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_CLOAK);
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+rail_send_server_power_display_request(RailServerContext* context,
+ const RAIL_POWER_DISPLAY_REQUEST* powerDisplayRequest)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!context || !powerDisplayRequest)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_POWER_DISPLAY_REQUEST_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rail_write_power_display_request_order(s, powerDisplayRequest);
+ error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_POWER_DISPLAY_REQUEST);
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error coie
+ */
+static UINT rail_send_server_get_app_id_resp(RailServerContext* context,
+ const RAIL_GET_APPID_RESP_ORDER* getAppidResp)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!context || !getAppidResp)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_GET_APPID_RESP_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rail_write_get_app_id_resp_order(s, getAppidResp);
+ error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_GET_APPID_RESP);
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_send_server_get_appid_resp_ex(RailServerContext* context,
+ const RAIL_GET_APPID_RESP_EX* getAppidRespEx)
+{
+ wStream* s = NULL;
+ UINT error = 0;
+
+ if (!context || !getAppidRespEx)
+ return ERROR_INVALID_PARAMETER;
+
+ s = rail_pdu_init(RAIL_GET_APPID_RESP_EX_ORDER_LENGTH);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "rail_pdu_init failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rail_write_get_appid_resp_ex_order(s, getAppidRespEx);
+ error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_GET_APPID_RESP_EX);
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_client_status_order(wStream* s, RAIL_CLIENT_STATUS_ORDER* clientStatus)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_CLIENT_STATUS_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, clientStatus->flags); /* Flags (4 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_exec_order(wStream* s, RAIL_EXEC_ORDER* exec)
+{
+ RAIL_EXEC_ORDER order = { 0 };
+ UINT16 exeLen = 0;
+ UINT16 workLen = 0;
+ UINT16 argLen = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_EXEC_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, exec->flags); /* Flags (2 bytes) */
+ Stream_Read_UINT16(s, exeLen); /* ExeOrFileLength (2 bytes) */
+ Stream_Read_UINT16(s, workLen); /* WorkingDirLength (2 bytes) */
+ Stream_Read_UINT16(s, argLen); /* ArgumentsLength (2 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)exeLen + workLen + argLen))
+ return ERROR_INVALID_DATA;
+
+ if (exeLen > 0)
+ {
+ const SSIZE_T len = exeLen / sizeof(WCHAR);
+ exec->RemoteApplicationProgram = Stream_Read_UTF16_String_As_UTF8(s, len, NULL);
+ if (!exec->RemoteApplicationProgram)
+ goto fail;
+ }
+ if (workLen > 0)
+ {
+ const SSIZE_T len = workLen / sizeof(WCHAR);
+ exec->RemoteApplicationWorkingDir = Stream_Read_UTF16_String_As_UTF8(s, len, NULL);
+ if (!exec->RemoteApplicationWorkingDir)
+ goto fail;
+ }
+ if (argLen > 0)
+ {
+ const SSIZE_T len = argLen / sizeof(WCHAR);
+ exec->RemoteApplicationArguments = Stream_Read_UTF16_String_As_UTF8(s, len, NULL);
+ if (!exec->RemoteApplicationArguments)
+ goto fail;
+ }
+
+ return CHANNEL_RC_OK;
+fail:
+ free(exec->RemoteApplicationProgram);
+ free(exec->RemoteApplicationArguments);
+ free(exec->RemoteApplicationWorkingDir);
+ *exec = order;
+ return ERROR_INTERNAL_ERROR;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_activate_order(wStream* s, RAIL_ACTIVATE_ORDER* activate)
+{
+ BYTE enabled = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_ACTIVATE_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, activate->windowId); /* WindowId (4 bytes) */
+ Stream_Read_UINT8(s, enabled); /* Enabled (1 byte) */
+ activate->enabled = (enabled != 0) ? TRUE : FALSE;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_sysmenu_order(wStream* s, RAIL_SYSMENU_ORDER* sysmenu)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_SYSMENU_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, sysmenu->windowId); /* WindowId (4 bytes) */
+ Stream_Read_INT16(s, sysmenu->left); /* Left (2 bytes) */
+ Stream_Read_INT16(s, sysmenu->top); /* Top (2 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_syscommand_order(wStream* s, RAIL_SYSCOMMAND_ORDER* syscommand)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_SYSCOMMAND_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, syscommand->windowId); /* WindowId (4 bytes) */
+ Stream_Read_UINT16(s, syscommand->command); /* Command (2 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_notify_event_order(wStream* s, RAIL_NOTIFY_EVENT_ORDER* notifyEvent)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_NOTIFY_EVENT_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, notifyEvent->windowId); /* WindowId (4 bytes) */
+ Stream_Read_UINT32(s, notifyEvent->notifyIconId); /* NotifyIconId (4 bytes) */
+ Stream_Read_UINT32(s, notifyEvent->message); /* Message (4 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_get_appid_req_order(wStream* s, RAIL_GET_APPID_REQ_ORDER* getAppidReq)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_GET_APPID_REQ_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, getAppidReq->windowId); /* WindowId (4 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_window_move_order(wStream* s, RAIL_WINDOW_MOVE_ORDER* windowMove)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_WINDOW_MOVE_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, windowMove->windowId); /* WindowId (4 bytes) */
+ Stream_Read_INT16(s, windowMove->left); /* Left (2 bytes) */
+ Stream_Read_INT16(s, windowMove->top); /* Top (2 bytes) */
+ Stream_Read_INT16(s, windowMove->right); /* Right (2 bytes) */
+ Stream_Read_INT16(s, windowMove->bottom); /* Bottom (2 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_snap_arange_order(wStream* s, RAIL_SNAP_ARRANGE* snapArrange)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_SNAP_ARRANGE_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, snapArrange->windowId); /* WindowId (4 bytes) */
+ Stream_Read_INT16(s, snapArrange->left); /* Left (2 bytes) */
+ Stream_Read_INT16(s, snapArrange->top); /* Top (2 bytes) */
+ Stream_Read_INT16(s, snapArrange->right); /* Right (2 bytes) */
+ Stream_Read_INT16(s, snapArrange->bottom); /* Bottom (2 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_langbar_info_order(wStream* s, RAIL_LANGBAR_INFO_ORDER* langbarInfo)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_LANGBAR_INFO_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, langbarInfo->languageBarStatus); /* LanguageBarStatus (4 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_language_ime_info_order(wStream* s,
+ RAIL_LANGUAGEIME_INFO_ORDER* languageImeInfo)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_LANGUAGEIME_INFO_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, languageImeInfo->ProfileType); /* ProfileType (4 bytes) */
+ Stream_Read_UINT16(s, languageImeInfo->LanguageID); /* LanguageID (2 bytes) */
+ Stream_Read(
+ s, &languageImeInfo->LanguageProfileCLSID,
+ sizeof(languageImeInfo->LanguageProfileCLSID)); /* LanguageProfileCLSID (16 bytes) */
+ Stream_Read(s, &languageImeInfo->ProfileGUID,
+ sizeof(languageImeInfo->ProfileGUID)); /* ProfileGUID (16 bytes) */
+ Stream_Read_UINT32(s, languageImeInfo->KeyboardLayout); /* KeyboardLayout (4 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_compartment_info_order(wStream* s,
+ RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_COMPARTMENT_INFO_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, compartmentInfo->ImeState); /* ImeState (4 bytes) */
+ Stream_Read_UINT32(s, compartmentInfo->ImeConvMode); /* ImeConvMode (4 bytes) */
+ Stream_Read_UINT32(s, compartmentInfo->ImeSentenceMode); /* ImeSentenceMode (4 bytes) */
+ Stream_Read_UINT32(s, compartmentInfo->KanaMode); /* KANAMode (4 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_read_cloak_order(wStream* s, RAIL_CLOAK* cloak)
+{
+ BYTE cloaked = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_CLOAK_ORDER_LENGTH))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, cloak->windowId); /* WindowId (4 bytes) */
+ Stream_Read_UINT8(s, cloaked); /* Cloaked (1 byte) */
+ cloak->cloak = (cloaked != 0) ? TRUE : FALSE;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_client_handshake_order(RailServerContext* context,
+ RAIL_HANDSHAKE_ORDER* handshake, wStream* s)
+{
+ UINT error = 0;
+
+ if (!context || !handshake || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_handshake_order(s, handshake)))
+ {
+ WLog_ERR(TAG, "rail_read_handshake_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ IFCALLRET(context->ClientHandshake, error, context, handshake);
+
+ if (error)
+ WLog_ERR(TAG, "context.ClientHandshake failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_client_client_status_order(RailServerContext* context,
+ RAIL_CLIENT_STATUS_ORDER* clientStatus, wStream* s)
+{
+ UINT error = 0;
+
+ if (!context || !clientStatus || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_client_status_order(s, clientStatus)))
+ {
+ WLog_ERR(TAG, "rail_read_client_status_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ IFCALLRET(context->ClientClientStatus, error, context, clientStatus);
+
+ if (error)
+ WLog_ERR(TAG, "context.ClientClientStatus failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_client_exec_order(RailServerContext* context, wStream* s)
+{
+ UINT error = 0;
+ RAIL_EXEC_ORDER exec = { 0 };
+
+ if (!context || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_exec_order(s, &exec)))
+ {
+ WLog_ERR(TAG, "rail_read_client_status_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ IFCALLRET(context->ClientExec, error, context, &exec);
+
+ if (error)
+ WLog_ERR(TAG, "context.Exec failed with error %" PRIu32 "", error);
+
+ free(exec.RemoteApplicationProgram);
+ free(exec.RemoteApplicationArguments);
+ free(exec.RemoteApplicationWorkingDir);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_client_sysparam_order(RailServerContext* context,
+ RAIL_SYSPARAM_ORDER* sysparam, wStream* s)
+{
+ UINT error = 0;
+ BOOL extendedSpiSupported = 0;
+
+ if (!context || !sysparam || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ extendedSpiSupported = rail_is_extended_spi_supported(context->priv->channelFlags);
+ if ((error = rail_read_sysparam_order(s, sysparam, extendedSpiSupported)))
+ {
+ WLog_ERR(TAG, "rail_read_sysparam_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ IFCALLRET(context->ClientSysparam, error, context, sysparam);
+
+ if (error)
+ WLog_ERR(TAG, "context.ClientSysparam failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_client_activate_order(RailServerContext* context,
+ RAIL_ACTIVATE_ORDER* activate, wStream* s)
+{
+ UINT error = 0;
+
+ if (!context || !activate || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_activate_order(s, activate)))
+ {
+ WLog_ERR(TAG, "rail_read_activate_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ IFCALLRET(context->ClientActivate, error, context, activate);
+
+ if (error)
+ WLog_ERR(TAG, "context.ClientActivate failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_client_sysmenu_order(RailServerContext* context, RAIL_SYSMENU_ORDER* sysmenu,
+ wStream* s)
+{
+ UINT error = 0;
+
+ if (!context || !sysmenu || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_sysmenu_order(s, sysmenu)))
+ {
+ WLog_ERR(TAG, "rail_read_sysmenu_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ IFCALLRET(context->ClientSysmenu, error, context, sysmenu);
+
+ if (error)
+ WLog_ERR(TAG, "context.ClientSysmenu failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_client_syscommand_order(RailServerContext* context,
+ RAIL_SYSCOMMAND_ORDER* syscommand, wStream* s)
+{
+ UINT error = 0;
+
+ if (!context || !syscommand || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_syscommand_order(s, syscommand)))
+ {
+ WLog_ERR(TAG, "rail_read_syscommand_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ IFCALLRET(context->ClientSyscommand, error, context, syscommand);
+
+ if (error)
+ WLog_ERR(TAG, "context.ClientSyscommand failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_client_notify_event_order(RailServerContext* context,
+ RAIL_NOTIFY_EVENT_ORDER* notifyEvent, wStream* s)
+{
+ UINT error = 0;
+
+ if (!context || !notifyEvent || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_notify_event_order(s, notifyEvent)))
+ {
+ WLog_ERR(TAG, "rail_read_notify_event_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ IFCALLRET(context->ClientNotifyEvent, error, context, notifyEvent);
+
+ if (error)
+ WLog_ERR(TAG, "context.ClientNotifyEvent failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_client_window_move_order(RailServerContext* context,
+ RAIL_WINDOW_MOVE_ORDER* windowMove, wStream* s)
+{
+ UINT error = 0;
+
+ if (!context || !windowMove || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_window_move_order(s, windowMove)))
+ {
+ WLog_ERR(TAG, "rail_read_window_move_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ IFCALLRET(context->ClientWindowMove, error, context, windowMove);
+
+ if (error)
+ WLog_ERR(TAG, "context.ClientWindowMove failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_client_snap_arrange_order(RailServerContext* context,
+ RAIL_SNAP_ARRANGE* snapArrange, wStream* s)
+{
+ UINT error = 0;
+
+ if (!context || !snapArrange || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_snap_arange_order(s, snapArrange)))
+ {
+ WLog_ERR(TAG, "rail_read_snap_arange_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ IFCALLRET(context->ClientSnapArrange, error, context, snapArrange);
+
+ if (error)
+ WLog_ERR(TAG, "context.ClientSnapArrange failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_client_get_appid_req_order(RailServerContext* context,
+ RAIL_GET_APPID_REQ_ORDER* getAppidReq, wStream* s)
+{
+ UINT error = 0;
+
+ if (!context || !getAppidReq || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_get_appid_req_order(s, getAppidReq)))
+ {
+ WLog_ERR(TAG, "rail_read_get_appid_req_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ IFCALLRET(context->ClientGetAppidReq, error, context, getAppidReq);
+
+ if (error)
+ WLog_ERR(TAG, "context.ClientGetAppidReq failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_client_langbar_info_order(RailServerContext* context,
+ RAIL_LANGBAR_INFO_ORDER* langbarInfo, wStream* s)
+{
+ UINT error = 0;
+
+ if (!context || !langbarInfo || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_langbar_info_order(s, langbarInfo)))
+ {
+ WLog_ERR(TAG, "rail_read_langbar_info_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ IFCALLRET(context->ClientLangbarInfo, error, context, langbarInfo);
+
+ if (error)
+ WLog_ERR(TAG, "context.ClientLangbarInfo failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_client_language_ime_info_order(RailServerContext* context,
+ RAIL_LANGUAGEIME_INFO_ORDER* languageImeInfo,
+ wStream* s)
+{
+ UINT error = 0;
+
+ if (!context || !languageImeInfo || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_language_ime_info_order(s, languageImeInfo)))
+ {
+ WLog_ERR(TAG, "rail_read_language_ime_info_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ IFCALLRET(context->ClientLanguageImeInfo, error, context, languageImeInfo);
+
+ if (error)
+ WLog_ERR(TAG, "context.ClientLanguageImeInfo failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_client_compartment_info(RailServerContext* context,
+ RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo,
+ wStream* s)
+{
+ UINT error = 0;
+
+ if (!context || !compartmentInfo || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_compartment_info_order(s, compartmentInfo)))
+ {
+ WLog_ERR(TAG, "rail_read_compartment_info_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ IFCALLRET(context->ClientCompartmentInfo, error, context, compartmentInfo);
+
+ if (error)
+ WLog_ERR(TAG, "context.ClientCompartmentInfo failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_recv_client_cloak_order(RailServerContext* context, RAIL_CLOAK* cloak, wStream* s)
+{
+ UINT error = 0;
+
+ if (!context || !cloak || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if ((error = rail_read_cloak_order(s, cloak)))
+ {
+ WLog_ERR(TAG, "rail_read_cloak_order failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ IFCALLRET(context->ClientCloak, error, context, cloak);
+
+ if (error)
+ WLog_ERR(TAG, "context.Cloak failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT rail_recv_client_text_scale_order(RailServerContext* context, wStream* s)
+{
+ UINT error = CHANNEL_RC_OK;
+ UINT32 TextScaleFactor = 0;
+
+ if (!context || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, TextScaleFactor);
+ IFCALLRET(context->ClientTextScale, error, context, TextScaleFactor);
+
+ if (error)
+ WLog_ERR(TAG, "context.TextScale failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT rail_recv_client_caret_blink(RailServerContext* context, wStream* s)
+{
+ UINT error = CHANNEL_RC_OK;
+ UINT32 CaretBlinkRate = 0;
+
+ if (!context || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, CaretBlinkRate);
+ IFCALLRET(context->ClientCaretBlinkRate, error, context, CaretBlinkRate);
+
+ if (error)
+ WLog_ERR(TAG, "context.CaretBlinkRate failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static DWORD WINAPI rail_server_thread(LPVOID arg)
+{
+ RailServerContext* context = (RailServerContext*)arg;
+ RailServerPrivate* priv = context->priv;
+ DWORD status = 0;
+ DWORD nCount = 0;
+ HANDLE events[8];
+ UINT error = CHANNEL_RC_OK;
+ events[nCount++] = priv->channelEvent;
+ events[nCount++] = priv->stopEvent;
+
+ while (TRUE)
+ {
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error);
+ break;
+ }
+
+ status = WaitForSingleObject(context->priv->stopEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
+ break;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ status = WaitForSingleObject(context->priv->channelEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(
+ TAG,
+ "WaitForSingleObject(context->priv->channelEvent, 0) failed with error %" PRIu32
+ "!",
+ error);
+ break;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ {
+ if ((error = rail_server_handle_messages(context)))
+ {
+ WLog_ERR(TAG, "rail_server_handle_messages failed with error %" PRIu32 "", error);
+ break;
+ }
+ }
+ }
+
+ if (error && context->rdpcontext)
+ setChannelError(context->rdpcontext, error, "rail_server_thread reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rail_server_start(RailServerContext* context)
+{
+ void* buffer = NULL;
+ DWORD bytesReturned = 0;
+ RailServerPrivate* priv = context->priv;
+ UINT error = ERROR_INTERNAL_ERROR;
+ priv->rail_channel =
+ WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, RAIL_SVC_CHANNEL_NAME);
+
+ if (!priv->rail_channel)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelOpen failed!");
+ return error;
+ }
+
+ if (!WTSVirtualChannelQuery(priv->rail_channel, WTSVirtualEventHandle, &buffer,
+ &bytesReturned) ||
+ (bytesReturned != sizeof(HANDLE)))
+ {
+ WLog_ERR(TAG,
+ "error during WTSVirtualChannelQuery(WTSVirtualEventHandle) or invalid returned "
+ "size(%" PRIu32 ")",
+ bytesReturned);
+
+ if (buffer)
+ WTSFreeMemory(buffer);
+
+ goto out_close;
+ }
+
+ CopyMemory(&priv->channelEvent, buffer, sizeof(HANDLE));
+ WTSFreeMemory(buffer);
+ context->priv->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (!context->priv->stopEvent)
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ goto out_close;
+ }
+
+ context->priv->thread = CreateThread(NULL, 0, rail_server_thread, (void*)context, 0, NULL);
+
+ if (!context->priv->thread)
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ goto out_stop_event;
+ }
+
+ return CHANNEL_RC_OK;
+out_stop_event:
+ CloseHandle(context->priv->stopEvent);
+ context->priv->stopEvent = NULL;
+out_close:
+ WTSVirtualChannelClose(context->priv->rail_channel);
+ context->priv->rail_channel = NULL;
+ return error;
+}
+
+static BOOL rail_server_stop(RailServerContext* context)
+{
+ RailServerPrivate* priv = (RailServerPrivate*)context->priv;
+
+ if (priv->thread)
+ {
+ SetEvent(priv->stopEvent);
+
+ if (WaitForSingleObject(priv->thread, INFINITE) == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", GetLastError());
+ return FALSE;
+ }
+
+ CloseHandle(priv->thread);
+ CloseHandle(priv->stopEvent);
+ priv->thread = NULL;
+ priv->stopEvent = NULL;
+ }
+
+ if (priv->rail_channel)
+ {
+ WTSVirtualChannelClose(priv->rail_channel);
+ priv->rail_channel = NULL;
+ }
+
+ priv->channelEvent = NULL;
+ return TRUE;
+}
+
+RailServerContext* rail_server_context_new(HANDLE vcm)
+{
+ RailServerContext* context = NULL;
+ RailServerPrivate* priv = NULL;
+ context = (RailServerContext*)calloc(1, sizeof(RailServerContext));
+
+ if (!context)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return NULL;
+ }
+
+ context->vcm = vcm;
+ context->Start = rail_server_start;
+ context->Stop = rail_server_stop;
+ context->ServerHandshake = rail_send_server_handshake;
+ context->ServerHandshakeEx = rail_send_server_handshake_ex;
+ context->ServerSysparam = rail_send_server_sysparam;
+ context->ServerLocalMoveSize = rail_send_server_local_move_size;
+ context->ServerMinMaxInfo = rail_send_server_min_max_info;
+ context->ServerTaskbarInfo = rail_send_server_taskbar_info;
+ context->ServerLangbarInfo = rail_send_server_langbar_info;
+ context->ServerExecResult = rail_send_server_exec_result;
+ context->ServerGetAppidResp = rail_send_server_get_app_id_resp;
+ context->ServerZOrderSync = rail_send_server_z_order_sync;
+ context->ServerCloak = rail_send_server_cloak;
+ context->ServerPowerDisplayRequest = rail_send_server_power_display_request;
+ context->ServerGetAppidRespEx = rail_send_server_get_appid_resp_ex;
+ context->priv = priv = (RailServerPrivate*)calloc(1, sizeof(RailServerPrivate));
+
+ if (!priv)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ goto out_free;
+ }
+
+ /* Create shared input stream */
+ priv->input_stream = Stream_New(NULL, 4096);
+
+ if (!priv->input_stream)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ goto out_free_priv;
+ }
+
+ return context;
+out_free_priv:
+ free(context->priv);
+out_free:
+ free(context);
+ return NULL;
+}
+
+void rail_server_context_free(RailServerContext* context)
+{
+ if (context->priv)
+ Stream_Free(context->priv->input_stream, TRUE);
+
+ free(context->priv);
+ free(context);
+}
+
+void rail_server_set_handshake_ex_flags(RailServerContext* context, DWORD flags)
+{
+ RailServerPrivate* priv = NULL;
+
+ if (!context || !context->priv)
+ return;
+
+ priv = context->priv;
+ priv->channelFlags = flags;
+}
+
+UINT rail_server_handle_messages(RailServerContext* context)
+{
+ char buffer[128] = { 0 };
+ UINT status = CHANNEL_RC_OK;
+ DWORD bytesReturned = 0;
+ UINT16 orderType = 0;
+ UINT16 orderLength = 0;
+ RailServerPrivate* priv = context->priv;
+ wStream* s = priv->input_stream;
+
+ /* Read header */
+ if (!Stream_EnsureRemainingCapacity(s, RAIL_PDU_HEADER_LENGTH))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed, RAIL_PDU_HEADER_LENGTH");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if (!WTSVirtualChannelRead(priv->rail_channel, 0, Stream_Pointer(s), RAIL_PDU_HEADER_LENGTH,
+ &bytesReturned))
+ {
+ if (GetLastError() == ERROR_NO_DATA)
+ return ERROR_NO_DATA;
+
+ WLog_ERR(TAG, "channel connection closed");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Parse header */
+ if ((status = rail_read_pdu_header(s, &orderType, &orderLength)) != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "rail_read_pdu_header failed with error %" PRIu32 "!", status);
+ return status;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, orderLength - RAIL_PDU_HEADER_LENGTH))
+ {
+ WLog_ERR(TAG,
+ "Stream_EnsureRemainingCapacity failed, orderLength - RAIL_PDU_HEADER_LENGTH");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ /* Read body */
+ if (!WTSVirtualChannelRead(priv->rail_channel, 0, Stream_Pointer(s),
+ orderLength - RAIL_PDU_HEADER_LENGTH, &bytesReturned))
+ {
+ if (GetLastError() == ERROR_NO_DATA)
+ return ERROR_NO_DATA;
+
+ WLog_ERR(TAG, "channel connection closed");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_DBG(TAG, "Received %s PDU, length:%" PRIu16 "",
+ rail_get_order_type_string_full(orderType, buffer, sizeof(buffer)), orderLength);
+
+ switch (orderType)
+ {
+ case TS_RAIL_ORDER_HANDSHAKE:
+ {
+ RAIL_HANDSHAKE_ORDER handshake;
+ return rail_recv_client_handshake_order(context, &handshake, s);
+ }
+
+ case TS_RAIL_ORDER_CLIENTSTATUS:
+ {
+ RAIL_CLIENT_STATUS_ORDER clientStatus;
+ return rail_recv_client_client_status_order(context, &clientStatus, s);
+ }
+
+ case TS_RAIL_ORDER_EXEC:
+ return rail_recv_client_exec_order(context, s);
+
+ case TS_RAIL_ORDER_SYSPARAM:
+ {
+ RAIL_SYSPARAM_ORDER sysparam = { 0 };
+ return rail_recv_client_sysparam_order(context, &sysparam, s);
+ }
+
+ case TS_RAIL_ORDER_ACTIVATE:
+ {
+ RAIL_ACTIVATE_ORDER activate;
+ return rail_recv_client_activate_order(context, &activate, s);
+ }
+
+ case TS_RAIL_ORDER_SYSMENU:
+ {
+ RAIL_SYSMENU_ORDER sysmenu;
+ return rail_recv_client_sysmenu_order(context, &sysmenu, s);
+ }
+
+ case TS_RAIL_ORDER_SYSCOMMAND:
+ {
+ RAIL_SYSCOMMAND_ORDER syscommand;
+ return rail_recv_client_syscommand_order(context, &syscommand, s);
+ }
+
+ case TS_RAIL_ORDER_NOTIFY_EVENT:
+ {
+ RAIL_NOTIFY_EVENT_ORDER notifyEvent;
+ return rail_recv_client_notify_event_order(context, &notifyEvent, s);
+ }
+
+ case TS_RAIL_ORDER_WINDOWMOVE:
+ {
+ RAIL_WINDOW_MOVE_ORDER windowMove;
+ return rail_recv_client_window_move_order(context, &windowMove, s);
+ }
+
+ case TS_RAIL_ORDER_SNAP_ARRANGE:
+ {
+ RAIL_SNAP_ARRANGE snapArrange;
+ return rail_recv_client_snap_arrange_order(context, &snapArrange, s);
+ }
+
+ case TS_RAIL_ORDER_GET_APPID_REQ:
+ {
+ RAIL_GET_APPID_REQ_ORDER getAppidReq;
+ return rail_recv_client_get_appid_req_order(context, &getAppidReq, s);
+ }
+
+ case TS_RAIL_ORDER_LANGBARINFO:
+ {
+ RAIL_LANGBAR_INFO_ORDER langbarInfo;
+ return rail_recv_client_langbar_info_order(context, &langbarInfo, s);
+ }
+
+ case TS_RAIL_ORDER_LANGUAGEIMEINFO:
+ {
+ RAIL_LANGUAGEIME_INFO_ORDER languageImeInfo;
+ return rail_recv_client_language_ime_info_order(context, &languageImeInfo, s);
+ }
+
+ case TS_RAIL_ORDER_COMPARTMENTINFO:
+ {
+ RAIL_COMPARTMENT_INFO_ORDER compartmentInfo;
+ return rail_recv_client_compartment_info(context, &compartmentInfo, s);
+ }
+
+ case TS_RAIL_ORDER_CLOAK:
+ {
+ RAIL_CLOAK cloak;
+ return rail_recv_client_cloak_order(context, &cloak, s);
+ }
+
+ case TS_RAIL_ORDER_TEXTSCALEINFO:
+ {
+ return rail_recv_client_text_scale_order(context, s);
+ }
+
+ case TS_RAIL_ORDER_CARETBLINKINFO:
+ {
+ return rail_recv_client_caret_blink(context, s);
+ }
+
+ default:
+ WLog_ERR(TAG, "Unknown RAIL PDU order received.");
+ return ERROR_INVALID_DATA;
+ }
+}
diff --git a/channels/rail/server/rail_main.h b/channels/rail/server/rail_main.h
new file mode 100644
index 0000000..f15cf19
--- /dev/null
+++ b/channels/rail/server/rail_main.h
@@ -0,0 +1,44 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RAIL Virtual Channel Plugin
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RAIL_SERVER_MAIN_H
+#define FREERDP_CHANNEL_RAIL_SERVER_MAIN_H
+
+#include <freerdp/rail.h>
+#include <freerdp/server/rail.h>
+
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+#include <winpr/stream.h>
+
+#include "../rail_common.h"
+
+struct s_rail_server_private
+{
+ HANDLE thread;
+ HANDLE stopEvent;
+ HANDLE channelEvent;
+ void* rail_channel;
+
+ wStream* input_stream;
+
+ DWORD channelFlags;
+};
+
+#endif /* FREERDP_CHANNEL_RAIL_SERVER_MAIN_H */
diff --git a/channels/rdp2tcp/CMakeLists.txt b/channels/rdp2tcp/CMakeLists.txt
new file mode 100644
index 0000000..31de127
--- /dev/null
+++ b/channels/rdp2tcp/CMakeLists.txt
@@ -0,0 +1,22 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("rdp2tcp")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/rdp2tcp/ChannelOptions.cmake b/channels/rdp2tcp/ChannelOptions.cmake
new file mode 100644
index 0000000..3cad9b1
--- /dev/null
+++ b/channels/rdp2tcp/ChannelOptions.cmake
@@ -0,0 +1,10 @@
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT OFF)
+
+define_channel_options(NAME "rdp2tcp" TYPE "static"
+ DESCRIPTION "Tunneling TCP over RDP"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
diff --git a/channels/rdp2tcp/client/CMakeLists.txt b/channels/rdp2tcp/client/CMakeLists.txt
new file mode 100644
index 0000000..f7bdd20
--- /dev/null
+++ b/channels/rdp2tcp/client/CMakeLists.txt
@@ -0,0 +1,27 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("rdp2tcp")
+
+set(${MODULE_PREFIX}_SRCS
+ rdp2tcp_main.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "VirtualChannelEntryEx")
diff --git a/channels/rdp2tcp/client/rdp2tcp_main.c b/channels/rdp2tcp/client/rdp2tcp_main.c
new file mode 100644
index 0000000..0b2c8f1
--- /dev/null
+++ b/channels/rdp2tcp/client/rdp2tcp_main.c
@@ -0,0 +1,357 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * rdp2tcp Virtual Channel Extension
+ *
+ * Copyright 2017 Artur Zaprzala
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <winpr/assert.h>
+
+#include <winpr/file.h>
+#include <winpr/pipe.h>
+#include <winpr/thread.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/svc.h>
+#include <freerdp/channels/rdp2tcp.h>
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG(RDP2TCP_DVC_CHANNEL_NAME)
+
+static int const debug = 0;
+
+typedef struct
+{
+ HANDLE hStdOutputRead;
+ HANDLE hStdInputWrite;
+ HANDLE hProcess;
+ HANDLE copyThread;
+ HANDLE writeComplete;
+ DWORD openHandle;
+ void* initHandle;
+ CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints;
+ char buffer[16 * 1024];
+ char* commandline;
+} Plugin;
+
+static int init_external_addin(Plugin* plugin)
+{
+ SECURITY_ATTRIBUTES saAttr;
+ STARTUPINFOA siStartInfo; /* Using ANSI type to match CreateProcessA */
+ PROCESS_INFORMATION procInfo;
+ saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
+ saAttr.bInheritHandle = TRUE;
+ saAttr.lpSecurityDescriptor = NULL;
+ siStartInfo.cb = sizeof(STARTUPINFO);
+ siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+ siStartInfo.dwFlags = STARTF_USESTDHANDLES;
+
+ // Create pipes
+ if (!CreatePipe(&plugin->hStdOutputRead, &siStartInfo.hStdOutput, &saAttr, 0))
+ {
+ WLog_ERR(TAG, "stdout CreatePipe");
+ return -1;
+ }
+
+ if (!SetHandleInformation(plugin->hStdOutputRead, HANDLE_FLAG_INHERIT, 0))
+ {
+ WLog_ERR(TAG, "stdout SetHandleInformation");
+ return -1;
+ }
+
+ if (!CreatePipe(&siStartInfo.hStdInput, &plugin->hStdInputWrite, &saAttr, 0))
+ {
+ WLog_ERR(TAG, "stdin CreatePipe");
+ return -1;
+ }
+
+ if (!SetHandleInformation(plugin->hStdInputWrite, HANDLE_FLAG_INHERIT, 0))
+ {
+ WLog_ERR(TAG, "stdin SetHandleInformation");
+ return -1;
+ }
+
+ // Execute plugin
+ plugin->commandline = _strdup(plugin->channelEntryPoints.pExtendedData);
+ if (!CreateProcessA(NULL,
+ plugin->commandline, // command line
+ NULL, // process security attributes
+ NULL, // primary thread security attributes
+ TRUE, // handles are inherited
+ 0, // creation flags
+ NULL, // use parent's environment
+ NULL, // use parent's current directory
+ &siStartInfo, // STARTUPINFO pointer
+ &procInfo // receives PROCESS_INFORMATION
+ ))
+ {
+ WLog_ERR(TAG, "fork for addin");
+ return -1;
+ }
+
+ plugin->hProcess = procInfo.hProcess;
+ CloseHandle(procInfo.hThread);
+ CloseHandle(siStartInfo.hStdOutput);
+ CloseHandle(siStartInfo.hStdInput);
+ return 0;
+}
+
+static void dumpData(char* data, unsigned length)
+{
+ unsigned const limit = 98;
+ unsigned l = length > limit ? limit / 2 : length;
+
+ for (unsigned i = 0; i < l; ++i)
+ {
+ printf("%02hhx", data[i]);
+ }
+
+ if (length > limit)
+ {
+ printf("...");
+
+ for (unsigned i = length - l; i < length; ++i)
+ printf("%02hhx", data[i]);
+ }
+
+ puts("");
+}
+
+static DWORD WINAPI copyThread(void* data)
+{
+ DWORD status = WAIT_OBJECT_0;
+ Plugin* plugin = (Plugin*)data;
+ size_t const bufsize = 16 * 1024;
+
+ while (status == WAIT_OBJECT_0)
+ {
+
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD dwRead = 0;
+ char* buffer = malloc(bufsize);
+
+ if (!buffer)
+ {
+ fprintf(stderr, "rdp2tcp copyThread: malloc failed\n");
+ goto fail;
+ }
+
+ // if (!ReadFile(plugin->hStdOutputRead, plugin->buffer, sizeof plugin->buffer, &dwRead,
+ // NULL))
+ if (!ReadFile(plugin->hStdOutputRead, buffer, bufsize, &dwRead, NULL))
+ {
+ free(buffer);
+ goto fail;
+ }
+
+ if (debug > 1)
+ {
+ printf(">%8u ", (unsigned)dwRead);
+ dumpData(buffer, dwRead);
+ }
+
+ if (plugin->channelEntryPoints.pVirtualChannelWriteEx(
+ plugin->initHandle, plugin->openHandle, buffer, dwRead, buffer) != CHANNEL_RC_OK)
+ {
+ free(buffer);
+ fprintf(stderr, "rdp2tcp copyThread failed %i\n", (int)dwRead);
+ goto fail;
+ }
+
+ handles[0] = plugin->writeComplete;
+ handles[1] = freerdp_abort_event(plugin->channelEntryPoints.context);
+ status = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
+ if (status == WAIT_OBJECT_0)
+ ResetEvent(plugin->writeComplete);
+ }
+
+fail:
+ ExitThread(0);
+ return 0;
+}
+
+static void closeChannel(Plugin* plugin)
+{
+ if (debug)
+ puts("rdp2tcp closing channel");
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(plugin->channelEntryPoints.pVirtualChannelCloseEx);
+ plugin->channelEntryPoints.pVirtualChannelCloseEx(plugin->initHandle, plugin->openHandle);
+}
+
+static void dataReceived(Plugin* plugin, void* pData, UINT32 dataLength, UINT32 totalLength,
+ UINT32 dataFlags)
+{
+ DWORD dwWritten = 0;
+
+ if (dataFlags & CHANNEL_FLAG_SUSPEND)
+ {
+ if (debug)
+ puts("rdp2tcp Channel Suspend");
+
+ return;
+ }
+
+ if (dataFlags & CHANNEL_FLAG_RESUME)
+ {
+ if (debug)
+ puts("rdp2tcp Channel Resume");
+
+ return;
+ }
+
+ if (debug > 1)
+ {
+ printf("<%c%3u/%3u ", dataFlags & CHANNEL_FLAG_FIRST ? ' ' : '+', totalLength, dataLength);
+ dumpData(pData, dataLength);
+ }
+
+ if (dataFlags & CHANNEL_FLAG_FIRST)
+ {
+ if (!WriteFile(plugin->hStdInputWrite, &totalLength, sizeof(totalLength), &dwWritten, NULL))
+ closeChannel(plugin);
+ }
+
+ if (!WriteFile(plugin->hStdInputWrite, pData, dataLength, &dwWritten, NULL))
+ closeChannel(plugin);
+}
+
+static void VCAPITYPE VirtualChannelOpenEventEx(LPVOID lpUserParam, DWORD openHandle, UINT event,
+ LPVOID pData, UINT32 dataLength, UINT32 totalLength,
+ UINT32 dataFlags)
+{
+ Plugin* plugin = (Plugin*)lpUserParam;
+
+ switch (event)
+ {
+ case CHANNEL_EVENT_DATA_RECEIVED:
+ dataReceived(plugin, pData, dataLength, totalLength, dataFlags);
+ break;
+
+ case CHANNEL_EVENT_WRITE_CANCELLED:
+ free(pData);
+ break;
+ case CHANNEL_EVENT_WRITE_COMPLETE:
+ SetEvent(plugin->writeComplete);
+ free(pData);
+ break;
+ }
+}
+
+static void channel_terminated(Plugin* plugin)
+{
+ if (debug)
+ puts("rdp2tcp terminated");
+
+ if (!plugin)
+ return;
+
+ if (plugin->copyThread)
+ TerminateThread(plugin->copyThread, 0);
+ if (plugin->writeComplete)
+ CloseHandle(plugin->writeComplete);
+
+ CloseHandle(plugin->hStdInputWrite);
+ CloseHandle(plugin->hStdOutputRead);
+ TerminateProcess(plugin->hProcess, 0);
+ CloseHandle(plugin->hProcess);
+ free(plugin->commandline);
+ free(plugin);
+}
+
+static void channel_initialized(Plugin* plugin)
+{
+ plugin->writeComplete = CreateEvent(NULL, TRUE, FALSE, NULL);
+ plugin->copyThread = CreateThread(NULL, 0, copyThread, plugin, 0, NULL);
+}
+
+static VOID VCAPITYPE VirtualChannelInitEventEx(LPVOID lpUserParam, LPVOID pInitHandle, UINT event,
+ LPVOID pData, UINT dataLength)
+{
+ Plugin* plugin = (Plugin*)lpUserParam;
+
+ switch (event)
+ {
+ case CHANNEL_EVENT_INITIALIZED:
+ channel_initialized(plugin);
+ break;
+
+ case CHANNEL_EVENT_CONNECTED:
+ if (debug)
+ puts("rdp2tcp connected");
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(plugin->channelEntryPoints.pVirtualChannelOpenEx);
+ if (plugin->channelEntryPoints.pVirtualChannelOpenEx(
+ pInitHandle, &plugin->openHandle, RDP2TCP_DVC_CHANNEL_NAME,
+ VirtualChannelOpenEventEx) != CHANNEL_RC_OK)
+ return;
+
+ break;
+
+ case CHANNEL_EVENT_DISCONNECTED:
+ if (debug)
+ puts("rdp2tcp disconnected");
+
+ break;
+
+ case CHANNEL_EVENT_TERMINATED:
+ channel_terminated(plugin);
+ break;
+ }
+}
+
+#if 1
+#define VirtualChannelEntryEx rdp2tcp_VirtualChannelEntryEx
+#else
+#define VirtualChannelEntryEx FREERDP_API VirtualChannelEntryEx
+#endif
+FREERDP_ENTRY_POINT(BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS pEntryPoints,
+ PVOID pInitHandle))
+{
+ CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx = NULL;
+ CHANNEL_DEF channelDef;
+ Plugin* plugin = (Plugin*)calloc(1, sizeof(Plugin));
+
+ if (!plugin)
+ return FALSE;
+
+ pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints;
+ WINPR_ASSERT(pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX) &&
+ pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER);
+ plugin->initHandle = pInitHandle;
+ plugin->channelEntryPoints = *pEntryPointsEx;
+
+ if (init_external_addin(plugin) < 0)
+ {
+ free(plugin);
+ return FALSE;
+ }
+
+ strncpy(channelDef.name, RDP2TCP_DVC_CHANNEL_NAME, sizeof(channelDef.name));
+ channelDef.options =
+ CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP | CHANNEL_OPTION_COMPRESS_RDP;
+
+ if (pEntryPointsEx->pVirtualChannelInitEx(plugin, NULL, pInitHandle, &channelDef, 1,
+ VIRTUAL_CHANNEL_VERSION_WIN2000,
+ VirtualChannelInitEventEx) != CHANNEL_RC_OK)
+ return FALSE;
+
+ return TRUE;
+}
+
+// vim:ts=4
diff --git a/channels/rdpdr/CMakeLists.txt b/channels/rdpdr/CMakeLists.txt
new file mode 100644
index 0000000..e9b4181
--- /dev/null
+++ b/channels/rdpdr/CMakeLists.txt
@@ -0,0 +1,26 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("rdpdr")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/rdpdr/ChannelOptions.cmake b/channels/rdpdr/ChannelOptions.cmake
new file mode 100644
index 0000000..9a96676
--- /dev/null
+++ b/channels/rdpdr/ChannelOptions.cmake
@@ -0,0 +1,13 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT ON)
+
+define_channel_options(NAME "rdpdr" TYPE "static"
+ DESCRIPTION "Device Redirection Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPEFS] [MS-RDPEPC] [MS-RDPESC] [MS-RDPESP]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/rdpdr/client/CMakeLists.txt b/channels/rdpdr/client/CMakeLists.txt
new file mode 100644
index 0000000..7d11574
--- /dev/null
+++ b/channels/rdpdr/client/CMakeLists.txt
@@ -0,0 +1,42 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+# Copyright 2016 Inuvika Inc.
+# Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("rdpdr")
+
+set(${MODULE_PREFIX}_SRCS
+ irp.c
+ irp.h
+ devman.c
+ devman.h
+ rdpdr_main.c
+ rdpdr_main.h
+ rdpdr_capabilities.c
+ rdpdr_capabilities.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr freerdp
+)
+if(APPLE AND (NOT IOS))
+ find_library(CORESERVICES_LIBRARY CoreServices)
+ list(APPEND ${MODULE_PREFIX}_LIBS
+ ${CORESERVICES_LIBRARY}
+ )
+endif()
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx")
diff --git a/channels/rdpdr/client/devman.c b/channels/rdpdr/client/devman.c
new file mode 100644
index 0000000..7c9e3ff
--- /dev/null
+++ b/channels/rdpdr/client/devman.c
@@ -0,0 +1,236 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Device Redirection Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 Armin Novak <armin.novak@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+
+#include <freerdp/types.h>
+#include <freerdp/addin.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/channels/log.h>
+
+#include "rdpdr_main.h"
+
+#include "devman.h"
+
+#define TAG CHANNELS_TAG("rdpdr.client")
+
+static void devman_device_free(void* obj)
+{
+ DEVICE* device = (DEVICE*)obj;
+
+ if (!device)
+ return;
+
+ IFCALL(device->Free, device);
+}
+
+DEVMAN* devman_new(rdpdrPlugin* rdpdr)
+{
+ DEVMAN* devman = NULL;
+
+ if (!rdpdr)
+ return NULL;
+
+ devman = (DEVMAN*)calloc(1, sizeof(DEVMAN));
+
+ if (!devman)
+ {
+ WLog_Print(rdpdr->log, WLOG_INFO, "calloc failed!");
+ return NULL;
+ }
+
+ devman->plugin = (void*)rdpdr;
+ devman->id_sequence = 1;
+ devman->devices = ListDictionary_New(TRUE);
+
+ if (!devman->devices)
+ {
+ WLog_Print(rdpdr->log, WLOG_INFO, "ListDictionary_New failed!");
+ free(devman);
+ return NULL;
+ }
+
+ ListDictionary_ValueObject(devman->devices)->fnObjectFree = devman_device_free;
+ return devman;
+}
+
+void devman_free(DEVMAN* devman)
+{
+ ListDictionary_Free(devman->devices);
+ free(devman);
+}
+
+void devman_unregister_device(DEVMAN* devman, void* key)
+{
+ DEVICE* device = NULL;
+
+ if (!devman || !key)
+ return;
+
+ device = (DEVICE*)ListDictionary_Take(devman->devices, key);
+
+ if (device)
+ devman_device_free(device);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT devman_register_device(DEVMAN* devman, DEVICE* device)
+{
+ void* key = NULL;
+
+ if (!devman || !device)
+ return ERROR_INVALID_PARAMETER;
+
+ device->id = devman->id_sequence++;
+ key = (void*)(size_t)device->id;
+
+ if (!ListDictionary_Add(devman->devices, key, device))
+ {
+ WLog_INFO(TAG, "ListDictionary_Add failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+DEVICE* devman_get_device_by_id(DEVMAN* devman, UINT32 id)
+{
+ DEVICE* device = NULL;
+ void* key = (void*)(size_t)id;
+
+ if (!devman)
+ {
+ WLog_ERR(TAG, "device manager=%p", devman);
+ return NULL;
+ }
+
+ device = (DEVICE*)ListDictionary_GetItemValue(devman->devices, key);
+ if (!device)
+ WLog_WARN(TAG, "could not find device ID 0x%08" PRIx32, id);
+ return device;
+}
+
+DEVICE* devman_get_device_by_type(DEVMAN* devman, UINT32 type)
+{
+ DEVICE* device = NULL;
+ ULONG_PTR* keys = NULL;
+
+ if (!devman)
+ return NULL;
+
+ ListDictionary_Lock(devman->devices);
+ const size_t count = ListDictionary_GetKeys(devman->devices, &keys);
+
+ for (size_t x = 0; x < count; x++)
+ {
+ DEVICE* cur = (DEVICE*)ListDictionary_GetItemValue(devman->devices, (void*)keys[x]);
+
+ if (!cur)
+ continue;
+
+ if (cur->type != type)
+ continue;
+
+ device = cur;
+ break;
+ }
+
+ free(keys);
+ ListDictionary_Unlock(devman->devices);
+ return device;
+}
+
+static const char DRIVE_SERVICE_NAME[] = "drive";
+static const char PRINTER_SERVICE_NAME[] = "printer";
+static const char SMARTCARD_SERVICE_NAME[] = "smartcard";
+static const char SERIAL_SERVICE_NAME[] = "serial";
+static const char PARALLEL_SERVICE_NAME[] = "parallel";
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT devman_load_device_service(DEVMAN* devman, const RDPDR_DEVICE* device, rdpContext* rdpcontext)
+{
+ const char* ServiceName = NULL;
+ DEVICE_SERVICE_ENTRY_POINTS ep;
+ PDEVICE_SERVICE_ENTRY entry = NULL;
+ union
+ {
+ const RDPDR_DEVICE* cdp;
+ RDPDR_DEVICE* dp;
+ } devconv;
+
+ devconv.cdp = device;
+ if (!devman || !device || !rdpcontext)
+ return ERROR_INVALID_PARAMETER;
+
+ if (device->Type == RDPDR_DTYP_FILESYSTEM)
+ ServiceName = DRIVE_SERVICE_NAME;
+ else if (device->Type == RDPDR_DTYP_PRINT)
+ ServiceName = PRINTER_SERVICE_NAME;
+ else if (device->Type == RDPDR_DTYP_SMARTCARD)
+ ServiceName = SMARTCARD_SERVICE_NAME;
+ else if (device->Type == RDPDR_DTYP_SERIAL)
+ ServiceName = SERIAL_SERVICE_NAME;
+ else if (device->Type == RDPDR_DTYP_PARALLEL)
+ ServiceName = PARALLEL_SERVICE_NAME;
+
+ if (!ServiceName)
+ {
+ WLog_INFO(TAG, "ServiceName %s did not match!", ServiceName);
+ return ERROR_INVALID_NAME;
+ }
+
+ if (device->Name)
+ WLog_INFO(TAG, "Loading device service %s [%s] (static)", ServiceName, device->Name);
+ else
+ WLog_INFO(TAG, "Loading device service %s (static)", ServiceName);
+
+ entry = (PDEVICE_SERVICE_ENTRY)freerdp_load_channel_addin_entry(ServiceName, NULL,
+ "DeviceServiceEntry", 0);
+
+ if (!entry)
+ {
+ WLog_INFO(TAG, "freerdp_load_channel_addin_entry failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ ep.devman = devman;
+ ep.RegisterDevice = devman_register_device;
+ ep.device = devconv.dp;
+ ep.rdpcontext = rdpcontext;
+ return entry(&ep);
+}
diff --git a/channels/rdpdr/client/devman.h b/channels/rdpdr/client/devman.h
new file mode 100644
index 0000000..2e6019e
--- /dev/null
+++ b/channels/rdpdr/client/devman.h
@@ -0,0 +1,36 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Device Redirection Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPDR_CLIENT_DEVMAN_H
+#define FREERDP_CHANNEL_RDPDR_CLIENT_DEVMAN_H
+
+#include "rdpdr_main.h"
+
+void devman_unregister_device(DEVMAN* devman, void* key);
+UINT devman_load_device_service(DEVMAN* devman, const RDPDR_DEVICE* device, rdpContext* rdpcontext);
+DEVICE* devman_get_device_by_id(DEVMAN* devman, UINT32 id);
+DEVICE* devman_get_device_by_type(DEVMAN* devman, UINT32 type);
+
+DEVMAN* devman_new(rdpdrPlugin* rdpdr);
+void devman_free(DEVMAN* devman);
+
+#endif /* FREERDP_CHANNEL_RDPDR_CLIENT_DEVMAN_H */
diff --git a/channels/rdpdr/client/irp.c b/channels/rdpdr/client/irp.c
new file mode 100644
index 0000000..eeba80d
--- /dev/null
+++ b/channels/rdpdr/client/irp.c
@@ -0,0 +1,165 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Device Redirection Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+
+#include <freerdp/utils/rdpdr_utils.h>
+
+#include "rdpdr_main.h"
+#include "devman.h"
+#include "irp.h"
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT irp_free(IRP* irp)
+{
+ if (!irp)
+ return CHANNEL_RC_OK;
+
+ if (irp->input)
+ Stream_Release(irp->input);
+ if (irp->output)
+ Stream_Release(irp->output);
+
+ winpr_aligned_free(irp);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT irp_complete(IRP* irp)
+{
+ size_t pos = 0;
+ rdpdrPlugin* rdpdr = NULL;
+ UINT error = 0;
+
+ WINPR_ASSERT(irp);
+ WINPR_ASSERT(irp->output);
+ WINPR_ASSERT(irp->devman);
+
+ rdpdr = (rdpdrPlugin*)irp->devman->plugin;
+ WINPR_ASSERT(rdpdr);
+
+ pos = Stream_GetPosition(irp->output);
+ Stream_SetPosition(irp->output, RDPDR_DEVICE_IO_RESPONSE_LENGTH - 4);
+ Stream_Write_UINT32(irp->output, irp->IoStatus); /* IoStatus (4 bytes) */
+ Stream_SetPosition(irp->output, pos);
+
+ error = rdpdr_send(rdpdr, irp->output);
+ irp->output = NULL;
+
+ irp_free(irp);
+ return error;
+}
+
+IRP* irp_new(DEVMAN* devman, wStreamPool* pool, wStream* s, wLog* log, UINT* error)
+{
+ IRP* irp = NULL;
+ DEVICE* device = NULL;
+ UINT32 DeviceId = 0;
+
+ WINPR_ASSERT(devman);
+ WINPR_ASSERT(pool);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 20))
+ {
+ if (error)
+ *error = ERROR_INVALID_DATA;
+ return NULL;
+ }
+
+ Stream_Read_UINT32(s, DeviceId); /* DeviceId (4 bytes) */
+ device = devman_get_device_by_id(devman, DeviceId);
+
+ if (!device)
+ {
+ if (error)
+ *error = ERROR_DEV_NOT_EXIST;
+
+ return NULL;
+ }
+
+ irp = (IRP*)winpr_aligned_malloc(sizeof(IRP), MEMORY_ALLOCATION_ALIGNMENT);
+
+ if (!irp)
+ {
+ WLog_Print(log, WLOG_ERROR, "_aligned_malloc failed!");
+ if (error)
+ *error = CHANNEL_RC_NO_MEMORY;
+ return NULL;
+ }
+
+ ZeroMemory(irp, sizeof(IRP));
+
+ Stream_Read_UINT32(s, irp->FileId); /* FileId (4 bytes) */
+ Stream_Read_UINT32(s, irp->CompletionId); /* CompletionId (4 bytes) */
+ Stream_Read_UINT32(s, irp->MajorFunction); /* MajorFunction (4 bytes) */
+ Stream_Read_UINT32(s, irp->MinorFunction); /* MinorFunction (4 bytes) */
+
+ Stream_AddRef(s);
+ irp->input = s;
+ irp->device = device;
+ irp->devman = devman;
+
+ irp->output = StreamPool_Take(pool, 256);
+ if (!irp->output)
+ {
+ WLog_Print(log, WLOG_ERROR, "Stream_New failed!");
+ irp_free(irp);
+ if (error)
+ *error = CHANNEL_RC_NO_MEMORY;
+ return NULL;
+ }
+
+ if (!rdpdr_write_iocompletion_header(irp->output, DeviceId, irp->CompletionId, 0))
+ {
+ irp_free(irp);
+ if (error)
+ *error = CHANNEL_RC_NO_MEMORY;
+ return NULL;
+ }
+
+ irp->Complete = irp_complete;
+ irp->Discard = irp_free;
+
+ irp->thread = NULL;
+ irp->cancelled = FALSE;
+
+ if (error)
+ *error = CHANNEL_RC_OK;
+
+ return irp;
+}
diff --git a/channels/rdpdr/client/irp.h b/channels/rdpdr/client/irp.h
new file mode 100644
index 0000000..0538295
--- /dev/null
+++ b/channels/rdpdr/client/irp.h
@@ -0,0 +1,29 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Device Redirection Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPDR_CLIENT_IRP_H
+#define FREERDP_CHANNEL_RDPDR_CLIENT_IRP_H
+
+#include <winpr/wlog.h>
+#include "rdpdr_main.h"
+
+IRP* irp_new(DEVMAN* devman, wStreamPool* pool, wStream* s, wLog* log, UINT* error);
+
+#endif /* FREERDP_CHANNEL_RDPDR_CLIENT_IRP_H */
diff --git a/channels/rdpdr/client/rdpdr_capabilities.c b/channels/rdpdr/client/rdpdr_capabilities.c
new file mode 100644
index 0000000..e094cc7
--- /dev/null
+++ b/channels/rdpdr/client/rdpdr_capabilities.c
@@ -0,0 +1,259 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Device Redirection Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015-2016 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+
+#include <freerdp/utils/rdpdr_utils.h>
+
+#include "rdpdr_main.h"
+#include "rdpdr_capabilities.h"
+
+#define RDPDR_CAPABILITY_HEADER_LENGTH 8
+
+/* Output device direction general capability set */
+static void rdpdr_write_general_capset(rdpdrPlugin* rdpdr, wStream* s)
+{
+ WINPR_UNUSED(rdpdr);
+ const RDPDR_CAPABILITY_HEADER header = { CAP_GENERAL_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH + 36,
+ GENERAL_CAPABILITY_VERSION_02 };
+
+ const UINT32 ioCode1 = rdpdr->clientIOCode1 & rdpdr->serverIOCode1;
+ const UINT32 ioCode2 = rdpdr->clientIOCode2 & rdpdr->serverIOCode2;
+
+ rdpdr_write_capset_header(rdpdr->log, s, &header);
+ Stream_Write_UINT32(s, rdpdr->clientOsType); /* osType, ignored on receipt */
+ Stream_Write_UINT32(s, rdpdr->clientOsVersion); /* osVersion, unused and must be set to zero */
+ Stream_Write_UINT16(s, rdpdr->clientVersionMajor); /* protocolMajorVersion, must be set to 1 */
+ Stream_Write_UINT16(s, rdpdr->clientVersionMinor); /* protocolMinorVersion */
+ Stream_Write_UINT32(s, ioCode1); /* ioCode1 */
+ Stream_Write_UINT32(s, ioCode2); /* ioCode2, must be set to zero, reserved for future use */
+ Stream_Write_UINT32(s, rdpdr->clientExtendedPDU); /* extendedPDU */
+ Stream_Write_UINT32(s, rdpdr->clientExtraFlags1); /* extraFlags1 */
+ Stream_Write_UINT32(
+ s,
+ rdpdr->clientExtraFlags2); /* extraFlags2, must be set to zero, reserved for future use */
+ Stream_Write_UINT32(
+ s, rdpdr->clientSpecialTypeDeviceCap); /* SpecialTypeDeviceCap, number of special devices to
+ be redirected before logon */
+}
+
+/* Process device direction general capability set */
+static UINT rdpdr_process_general_capset(rdpdrPlugin* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_ASSERT(header);
+
+ if (header->CapabilityLength != 36)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR,
+ "CAP_GENERAL_TYPE::CapabilityLength expected 36, got %" PRIu32,
+ header->CapabilityLength);
+ return ERROR_INVALID_DATA;
+ }
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 36))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, rdpdr->serverOsType); /* osType, ignored on receipt */
+ Stream_Read_UINT32(s, rdpdr->serverOsVersion); /* osVersion, unused and must be set to zero */
+ Stream_Read_UINT16(s, rdpdr->serverVersionMajor); /* protocolMajorVersion, must be set to 1 */
+ Stream_Read_UINT16(s, rdpdr->serverVersionMinor); /* protocolMinorVersion */
+ Stream_Read_UINT32(s, rdpdr->serverIOCode1); /* ioCode1 */
+ Stream_Read_UINT32(
+ s, rdpdr->serverIOCode2); /* ioCode2, must be set to zero, reserved for future use */
+ Stream_Read_UINT32(s, rdpdr->serverExtendedPDU); /* extendedPDU */
+ Stream_Read_UINT32(s, rdpdr->serverExtraFlags1); /* extraFlags1 */
+ Stream_Read_UINT32(
+ s,
+ rdpdr->serverExtraFlags2); /* extraFlags2, must be set to zero, reserved for future use */
+ Stream_Read_UINT32(
+ s, rdpdr->serverSpecialTypeDeviceCap); /* SpecialTypeDeviceCap, number of special devices to
+ be redirected before logon */
+ return CHANNEL_RC_OK;
+}
+
+/* Output printer direction capability set */
+static void rdpdr_write_printer_capset(rdpdrPlugin* rdpdr, wStream* s)
+{
+ WINPR_UNUSED(rdpdr);
+ const RDPDR_CAPABILITY_HEADER header = { CAP_PRINTER_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH,
+ PRINT_CAPABILITY_VERSION_01 };
+ rdpdr_write_capset_header(rdpdr->log, s, &header);
+}
+
+/* Process printer direction capability set */
+static UINT rdpdr_process_printer_capset(rdpdrPlugin* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_ASSERT(header);
+ Stream_Seek(s, header->CapabilityLength);
+ return CHANNEL_RC_OK;
+}
+
+/* Output port redirection capability set */
+static void rdpdr_write_port_capset(rdpdrPlugin* rdpdr, wStream* s)
+{
+ WINPR_UNUSED(rdpdr);
+ const RDPDR_CAPABILITY_HEADER header = { CAP_PORT_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH,
+ PORT_CAPABILITY_VERSION_01 };
+ rdpdr_write_capset_header(rdpdr->log, s, &header);
+}
+
+/* Process port redirection capability set */
+static UINT rdpdr_process_port_capset(rdpdrPlugin* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_ASSERT(header);
+ Stream_Seek(s, header->CapabilityLength);
+ return CHANNEL_RC_OK;
+}
+
+/* Output drive redirection capability set */
+static void rdpdr_write_drive_capset(rdpdrPlugin* rdpdr, wStream* s)
+{
+ WINPR_UNUSED(rdpdr);
+ const RDPDR_CAPABILITY_HEADER header = { CAP_DRIVE_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH,
+ DRIVE_CAPABILITY_VERSION_02 };
+ rdpdr_write_capset_header(rdpdr->log, s, &header);
+}
+
+/* Process drive redirection capability set */
+static UINT rdpdr_process_drive_capset(rdpdrPlugin* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_ASSERT(header);
+ Stream_Seek(s, header->CapabilityLength);
+ return CHANNEL_RC_OK;
+}
+
+/* Output smart card redirection capability set */
+static void rdpdr_write_smartcard_capset(rdpdrPlugin* rdpdr, wStream* s)
+{
+ WINPR_UNUSED(rdpdr);
+ const RDPDR_CAPABILITY_HEADER header = { CAP_SMARTCARD_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH,
+ SMARTCARD_CAPABILITY_VERSION_01 };
+ rdpdr_write_capset_header(rdpdr->log, s, &header);
+}
+
+/* Process smartcard redirection capability set */
+static UINT rdpdr_process_smartcard_capset(rdpdrPlugin* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_ASSERT(header);
+ Stream_Seek(s, header->CapabilityLength);
+ return CHANNEL_RC_OK;
+}
+
+UINT rdpdr_process_capability_request(rdpdrPlugin* rdpdr, wStream* s)
+{
+ UINT status = CHANNEL_RC_OK;
+ UINT16 numCapabilities = 0;
+
+ if (!rdpdr || !s)
+ return CHANNEL_RC_NULL_DATA;
+
+ WINPR_ASSERT(rdpdr->state == RDPDR_CHANNEL_STATE_SERVER_CAPS);
+ rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_CLIENT_CAPS);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, numCapabilities);
+ Stream_Seek(s, 2); /* pad (2 bytes) */
+
+ for (UINT16 i = 0; i < numCapabilities; i++)
+ {
+ RDPDR_CAPABILITY_HEADER header = { 0 };
+ UINT error = rdpdr_read_capset_header(rdpdr->log, s, &header);
+ if (error != CHANNEL_RC_OK)
+ return error;
+
+ switch (header.CapabilityType)
+ {
+ case CAP_GENERAL_TYPE:
+ status = rdpdr_process_general_capset(rdpdr, s, &header);
+ break;
+
+ case CAP_PRINTER_TYPE:
+ status = rdpdr_process_printer_capset(rdpdr, s, &header);
+ break;
+
+ case CAP_PORT_TYPE:
+ status = rdpdr_process_port_capset(rdpdr, s, &header);
+ break;
+
+ case CAP_DRIVE_TYPE:
+ status = rdpdr_process_drive_capset(rdpdr, s, &header);
+ break;
+
+ case CAP_SMARTCARD_TYPE:
+ status = rdpdr_process_smartcard_capset(rdpdr, s, &header);
+ break;
+
+ default:
+
+ break;
+ }
+
+ if (status != CHANNEL_RC_OK)
+ return status;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpdr_send_capability_response(rdpdrPlugin* rdpdr)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(rdpdr);
+ s = StreamPool_Take(rdpdr->pool, 256);
+
+ if (!s)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, RDPDR_CTYP_CORE);
+ Stream_Write_UINT16(s, PAKID_CORE_CLIENT_CAPABILITY);
+ Stream_Write_UINT16(s, 5); /* numCapabilities */
+ Stream_Write_UINT16(s, 0); /* pad */
+ rdpdr_write_general_capset(rdpdr, s);
+ rdpdr_write_printer_capset(rdpdr, s);
+ rdpdr_write_port_capset(rdpdr, s);
+ rdpdr_write_drive_capset(rdpdr, s);
+ rdpdr_write_smartcard_capset(rdpdr, s);
+ return rdpdr_send(rdpdr, s);
+}
diff --git a/channels/rdpdr/client/rdpdr_capabilities.h b/channels/rdpdr/client/rdpdr_capabilities.h
new file mode 100644
index 0000000..d4e1ecb
--- /dev/null
+++ b/channels/rdpdr/client/rdpdr_capabilities.h
@@ -0,0 +1,31 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Device Redirection Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPDR_CLIENT_CAPABILITIES_H
+#define FREERDP_CHANNEL_RDPDR_CLIENT_CAPABILITIES_H
+
+#include "rdpdr_main.h"
+
+UINT rdpdr_process_capability_request(rdpdrPlugin* rdpdr, wStream* s);
+UINT rdpdr_send_capability_response(rdpdrPlugin* rdpdr);
+
+#endif /* FREERDP_CHANNEL_RDPDR_CLIENT_CAPABILITIES_H */
diff --git a/channels/rdpdr/client/rdpdr_main.c b/channels/rdpdr/client/rdpdr_main.c
new file mode 100644
index 0000000..53f5011
--- /dev/null
+++ b/channels/rdpdr/client/rdpdr_main.c
@@ -0,0 +1,2342 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Device Redirection Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015-2016 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/sysinfo.h>
+#include <winpr/assert.h>
+#include <winpr/stream.h>
+
+#include <winpr/print.h>
+#include <winpr/sspicli.h>
+
+#include <freerdp/types.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/constants.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/utils/rdpdr_utils.h>
+
+#ifdef _WIN32
+#include <windows.h>
+#include <dbt.h>
+#else
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#endif
+
+#ifdef __MACOSX__
+#include <CoreFoundation/CoreFoundation.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+#include "rdpdr_capabilities.h"
+
+#include "devman.h"
+#include "irp.h"
+
+#include "rdpdr_main.h"
+
+#define TAG CHANNELS_TAG("rdpdr.client")
+
+/* IMPORTANT: Keep in sync with DRIVE_DEVICE */
+typedef struct
+{
+ DEVICE device;
+ WCHAR* path;
+ BOOL automount;
+} DEVICE_DRIVE_EXT;
+
+static const char* rdpdr_state_str(enum RDPDR_CHANNEL_STATE state)
+{
+ switch (state)
+ {
+ case RDPDR_CHANNEL_STATE_INITIAL:
+ return "RDPDR_CHANNEL_STATE_INITIAL";
+ case RDPDR_CHANNEL_STATE_ANNOUNCE:
+ return "RDPDR_CHANNEL_STATE_ANNOUNCE";
+ case RDPDR_CHANNEL_STATE_ANNOUNCE_REPLY:
+ return "RDPDR_CHANNEL_STATE_ANNOUNCE_REPLY";
+ case RDPDR_CHANNEL_STATE_NAME_REQUEST:
+ return "RDPDR_CHANNEL_STATE_NAME_REQUEST";
+ case RDPDR_CHANNEL_STATE_SERVER_CAPS:
+ return "RDPDR_CHANNEL_STATE_SERVER_CAPS";
+ case RDPDR_CHANNEL_STATE_CLIENT_CAPS:
+ return "RDPDR_CHANNEL_STATE_CLIENT_CAPS";
+ case RDPDR_CHANNEL_STATE_CLIENTID_CONFIRM:
+ return "RDPDR_CHANNEL_STATE_CLIENTID_CONFIRM";
+ case RDPDR_CHANNEL_STATE_READY:
+ return "RDPDR_CHANNEL_STATE_READY";
+ case RDPDR_CHANNEL_STATE_USER_LOGGEDON:
+ return "RDPDR_CHANNEL_STATE_USER_LOGGEDON";
+ default:
+ return "RDPDR_CHANNEL_STATE_UNKNOWN";
+ }
+}
+
+static const char* rdpdr_device_type_string(UINT32 type)
+{
+ switch (type)
+ {
+ case RDPDR_DTYP_SERIAL:
+ return "serial";
+ case RDPDR_DTYP_PRINT:
+ return "printer";
+ case RDPDR_DTYP_FILESYSTEM:
+ return "drive";
+ case RDPDR_DTYP_SMARTCARD:
+ return "smartcard";
+ case RDPDR_DTYP_PARALLEL:
+ return "parallel";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static const char* support_str(BOOL val)
+{
+ if (val)
+ return "supported";
+ return "not found";
+}
+
+static const char* rdpdr_caps_pdu_str(UINT32 flag)
+{
+ switch (flag)
+ {
+ case RDPDR_DEVICE_REMOVE_PDUS:
+ return "RDPDR_USER_LOGGEDON_PDU";
+ case RDPDR_CLIENT_DISPLAY_NAME_PDU:
+ return "RDPDR_CLIENT_DISPLAY_NAME_PDU";
+ case RDPDR_USER_LOGGEDON_PDU:
+ return "RDPDR_USER_LOGGEDON_PDU";
+ default:
+ return "RDPDR_UNKNONW";
+ }
+}
+
+static BOOL rdpdr_check_extended_pdu_flag(rdpdrPlugin* rdpdr, UINT32 flag)
+{
+ WINPR_ASSERT(rdpdr);
+
+ const BOOL client = (rdpdr->clientExtendedPDU & flag) != 0;
+ const BOOL server = (rdpdr->serverExtendedPDU & flag) != 0;
+
+ if (!client || !server)
+ {
+ WLog_Print(rdpdr->log, WLOG_WARN, "Checking ExtendedPDU::%s, client %s, server %s",
+ rdpdr_caps_pdu_str(flag), support_str(client), support_str(server));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL rdpdr_state_advance(rdpdrPlugin* rdpdr, enum RDPDR_CHANNEL_STATE next)
+{
+ WINPR_ASSERT(rdpdr);
+
+ if (next != rdpdr->state)
+ WLog_Print(rdpdr->log, WLOG_DEBUG, "[RDPDR] transition from %s to %s",
+ rdpdr_state_str(rdpdr->state), rdpdr_state_str(next));
+ rdpdr->state = next;
+ return TRUE;
+}
+
+static BOOL device_foreach(rdpdrPlugin* rdpdr, BOOL abortOnFail,
+ BOOL (*fkt)(ULONG_PTR key, void* element, void* data), void* data)
+{
+ BOOL rc = TRUE;
+ ULONG_PTR* keys = NULL;
+
+ ListDictionary_Lock(rdpdr->devman->devices);
+ const size_t count = ListDictionary_GetKeys(rdpdr->devman->devices, &keys);
+ for (size_t x = 0; x < count; x++)
+ {
+ void* element = ListDictionary_GetItemValue(rdpdr->devman->devices, (void*)keys[x]);
+ if (!fkt(keys[x], element, data))
+ {
+ rc = FALSE;
+ if (abortOnFail)
+ break;
+ }
+ }
+ free(keys);
+ ListDictionary_Unlock(rdpdr->devman->devices);
+ return rc;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_try_send_device_list_announce_request(rdpdrPlugin* rdpdr);
+
+static BOOL rdpdr_load_drive(rdpdrPlugin* rdpdr, const char* name, const char* path, BOOL automount)
+{
+ UINT rc = ERROR_INTERNAL_ERROR;
+ union
+ {
+ RDPDR_DRIVE* drive;
+ RDPDR_DEVICE* device;
+ } drive;
+ const char* args[] = { name, path, automount ? NULL : name };
+
+ drive.device = freerdp_device_new(RDPDR_DTYP_FILESYSTEM, ARRAYSIZE(args), args);
+ if (!drive.device)
+ goto fail;
+
+ rc = devman_load_device_service(rdpdr->devman, drive.device, rdpdr->rdpcontext);
+ if (rc != CHANNEL_RC_OK)
+ goto fail;
+
+fail:
+ freerdp_device_free(drive.device);
+ return rc;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_send_device_list_remove_request(rdpdrPlugin* rdpdr, UINT32 count, UINT32 ids[])
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(ids || (count == 0));
+
+ if (count == 0)
+ return CHANNEL_RC_OK;
+
+ if (!rdpdr_check_extended_pdu_flag(rdpdr, RDPDR_DEVICE_REMOVE_PDUS))
+ return CHANNEL_RC_OK;
+
+ s = StreamPool_Take(rdpdr->pool, count * sizeof(UINT32) + 8);
+
+ if (!s)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, RDPDR_CTYP_CORE);
+ Stream_Write_UINT16(s, PAKID_CORE_DEVICELIST_REMOVE);
+ Stream_Write_UINT32(s, count);
+
+ for (UINT32 i = 0; i < count; i++)
+ Stream_Write_UINT32(s, ids[i]);
+
+ Stream_SealLength(s);
+ return rdpdr_send(rdpdr, s);
+}
+
+#if defined(_UWP) || defined(__IOS__)
+
+static void first_hotplug(rdpdrPlugin* rdpdr)
+{
+}
+
+static DWORD WINAPI drive_hotplug_thread_func(LPVOID arg)
+{
+ return CHANNEL_RC_OK;
+}
+
+static UINT drive_hotplug_thread_terminate(rdpdrPlugin* rdpdr)
+{
+ return CHANNEL_RC_OK;
+}
+
+#elif defined(_WIN32)
+
+static BOOL check_path(const char* path)
+{
+ UINT type = GetDriveTypeA(path);
+
+ if (!(type == DRIVE_FIXED || type == DRIVE_REMOVABLE || type == DRIVE_CDROM ||
+ type == DRIVE_REMOTE))
+ return FALSE;
+
+ return GetVolumeInformationA(path, NULL, 0, NULL, NULL, NULL, NULL, 0);
+}
+
+static void first_hotplug(rdpdrPlugin* rdpdr)
+{
+ DWORD unitmask = GetLogicalDrives();
+
+ for (size_t i = 0; i < 26; i++)
+ {
+ if (unitmask & 0x01)
+ {
+ char drive_path[] = { 'c', ':', '\\', '\0' };
+ char drive_name[] = { 'c', '\0' };
+ drive_path[0] = 'A' + (char)i;
+ drive_name[0] = 'A' + (char)i;
+
+ if (check_path(drive_path))
+ {
+ rdpdr_load_drive(rdpdr, drive_name, drive_path, TRUE);
+ }
+ }
+
+ unitmask = unitmask >> 1;
+ }
+}
+
+static LRESULT CALLBACK hotplug_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
+{
+ rdpdrPlugin* rdpdr;
+ PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;
+ UINT error;
+ rdpdr = (rdpdrPlugin*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
+
+ switch (Msg)
+ {
+ case WM_DEVICECHANGE:
+ switch (wParam)
+ {
+ case DBT_DEVICEARRIVAL:
+ if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME)
+ {
+ PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb;
+ DWORD unitmask = lpdbv->dbcv_unitmask;
+
+ for (int i = 0; i < 26; i++)
+ {
+ if (unitmask & 0x01)
+ {
+ char drive_path[] = { 'c', ':', '/', '\0' };
+ char drive_name[] = { 'c', '\0' };
+ drive_path[0] = 'A' + (char)i;
+ drive_name[0] = 'A' + (char)i;
+
+ if (check_path(drive_path))
+ {
+ rdpdr_load_drive(rdpdr, drive_name, drive_path, TRUE);
+ rdpdr_try_send_device_list_announce_request(rdpdr);
+ }
+ }
+
+ unitmask = unitmask >> 1;
+ }
+ }
+
+ break;
+
+ case DBT_DEVICEREMOVECOMPLETE:
+ if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME)
+ {
+ PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb;
+ DWORD unitmask = lpdbv->dbcv_unitmask;
+ int count;
+ char drive_name_upper, drive_name_lower;
+ ULONG_PTR* keys = NULL;
+ DEVICE_DRIVE_EXT* device_ext;
+ UINT32 ids[1];
+
+ for (int i = 0; i < 26; i++)
+ {
+ if (unitmask & 0x01)
+ {
+ drive_name_upper = 'A' + i;
+ drive_name_lower = 'a' + i;
+ count = ListDictionary_GetKeys(rdpdr->devman->devices, &keys);
+
+ for (int j = 0; j < count; j++)
+ {
+ device_ext = (DEVICE_DRIVE_EXT*)ListDictionary_GetItemValue(
+ rdpdr->devman->devices, (void*)keys[j]);
+
+ if (device_ext->device.type != RDPDR_DTYP_FILESYSTEM)
+ continue;
+
+ if (device_ext->path[0] == drive_name_upper ||
+ device_ext->path[0] == drive_name_lower)
+ {
+ if (device_ext->automount)
+ {
+ devman_unregister_device(rdpdr->devman, (void*)keys[j]);
+ ids[0] = keys[j];
+
+ if ((error = rdpdr_send_device_list_remove_request(
+ rdpdr, 1, ids)))
+ {
+ // dont end on error, just report ?
+ WLog_Print(
+ rdpdr->log, WLOG_ERROR,
+ "rdpdr_send_device_list_remove_request failed "
+ "with error %" PRIu32 "!",
+ error);
+ }
+
+ break;
+ }
+ }
+ }
+
+ free(keys);
+ }
+
+ unitmask = unitmask >> 1;
+ }
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+
+ default:
+ return DefWindowProc(hWnd, Msg, wParam, lParam);
+ }
+
+ return DefWindowProc(hWnd, Msg, wParam, lParam);
+}
+
+static DWORD WINAPI drive_hotplug_thread_func(LPVOID arg)
+{
+ rdpdrPlugin* rdpdr;
+ WNDCLASSEX wnd_cls;
+ HWND hwnd;
+ MSG msg;
+ BOOL bRet;
+ DEV_BROADCAST_HANDLE NotificationFilter;
+ HDEVNOTIFY hDevNotify;
+ rdpdr = (rdpdrPlugin*)arg;
+ /* init windows class */
+ wnd_cls.cbSize = sizeof(WNDCLASSEX);
+ wnd_cls.style = CS_HREDRAW | CS_VREDRAW;
+ wnd_cls.lpfnWndProc = hotplug_proc;
+ wnd_cls.cbClsExtra = 0;
+ wnd_cls.cbWndExtra = 0;
+ wnd_cls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
+ wnd_cls.hCursor = NULL;
+ wnd_cls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
+ wnd_cls.lpszMenuName = NULL;
+ wnd_cls.lpszClassName = L"DRIVE_HOTPLUG";
+ wnd_cls.hInstance = NULL;
+ wnd_cls.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
+ RegisterClassEx(&wnd_cls);
+ /* create window */
+ hwnd = CreateWindowEx(0, L"DRIVE_HOTPLUG", NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL);
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)rdpdr);
+ rdpdr->hotplug_wnd = hwnd;
+ /* register device interface to hwnd */
+ NotificationFilter.dbch_size = sizeof(DEV_BROADCAST_HANDLE);
+ NotificationFilter.dbch_devicetype = DBT_DEVTYP_HANDLE;
+ hDevNotify = RegisterDeviceNotification(hwnd, &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE);
+
+ /* message loop */
+ while ((bRet = GetMessage(&msg, 0, 0, 0)) != 0)
+ {
+ if (bRet == -1)
+ {
+ break;
+ }
+ else
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ }
+
+ UnregisterDeviceNotification(hDevNotify);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_hotplug_thread_terminate(rdpdrPlugin* rdpdr)
+{
+ UINT error = CHANNEL_RC_OK;
+
+ if (rdpdr->hotplug_wnd && !PostMessage(rdpdr->hotplug_wnd, WM_QUIT, 0, 0))
+ {
+ error = GetLastError();
+ WLog_Print(rdpdr->log, WLOG_ERROR, "PostMessage failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+#elif defined(__MACOSX__)
+
+#define MAX_USB_DEVICES 100
+
+typedef struct
+{
+ char* path;
+ BOOL to_add;
+} hotplug_dev;
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT handle_hotplug(rdpdrPlugin* rdpdr)
+{
+ struct dirent* pDirent;
+ DIR* pDir;
+ char fullpath[PATH_MAX];
+ char* szdir = (char*)"/Volumes";
+ struct stat buf;
+ hotplug_dev dev_array[MAX_USB_DEVICES];
+ int count;
+ DEVICE_DRIVE_EXT* device_ext;
+ ULONG_PTR* keys = NULL;
+ int size = 0;
+ UINT error;
+ UINT32 ids[1];
+ pDir = opendir(szdir);
+
+ if (pDir == NULL)
+ {
+ printf("Cannot open directory\n");
+ return ERROR_OPEN_FAILED;
+ }
+
+ while ((pDirent = readdir(pDir)) != NULL)
+ {
+ if (pDirent->d_name[0] != '.')
+ {
+ sprintf_s(fullpath, ARRAYSIZE(fullpath), "%s/%s", szdir, pDirent->d_name);
+ if (stat(fullpath, &buf) != 0)
+ continue;
+
+ if (S_ISDIR(buf.st_mode))
+ {
+ dev_array[size].path = _strdup(fullpath);
+
+ if (!dev_array[size].path)
+ {
+ closedir(pDir);
+ error = CHANNEL_RC_NO_MEMORY;
+ goto cleanup;
+ }
+
+ dev_array[size++].to_add = TRUE;
+ }
+ }
+ }
+
+ closedir(pDir);
+ /* delete removed devices */
+ count = ListDictionary_GetKeys(rdpdr->devman->devices, &keys);
+
+ for (size_t j = 0; j < count; j++)
+ {
+ char* path = NULL;
+ BOOL dev_found = FALSE;
+ device_ext =
+ (DEVICE_DRIVE_EXT*)ListDictionary_GetItemValue(rdpdr->devman->devices, (void*)keys[j]);
+
+ if (!device_ext || !device_ext->automount)
+ continue;
+
+ if (device_ext->device.type != RDPDR_DTYP_FILESYSTEM)
+ continue;
+
+ if (device_ext->path == NULL)
+ continue;
+
+ path = ConvertWCharToUtf8Alloc(device_ext->path, NULL);
+ if (!path)
+ continue;
+
+ /* not plugable device */
+ if (strstr(path, "/Volumes/") == NULL)
+ {
+ free(path);
+ continue;
+ }
+
+ for (size_t i = 0; i < size; i++)
+ {
+ if (strstr(path, dev_array[i].path) != NULL)
+ {
+ dev_found = TRUE;
+ dev_array[i].to_add = FALSE;
+ break;
+ }
+ }
+
+ free(path);
+
+ if (!dev_found)
+ {
+ devman_unregister_device(rdpdr->devman, (void*)keys[j]);
+ ids[0] = keys[j];
+
+ if ((error = rdpdr_send_device_list_remove_request(rdpdr, 1, ids)))
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR,
+ "rdpdr_send_device_list_remove_request failed with error %" PRIu32 "!",
+ error);
+ goto cleanup;
+ }
+ }
+ }
+
+ /* add new devices */
+ for (size_t i = 0; i < size; i++)
+ {
+ const hotplug_dev* dev = &dev_array[i];
+ if (dev->to_add)
+ {
+ const char* path = dev->path;
+ const char* name = strrchr(path, '/') + 1;
+ error = rdpdr_load_drive(rdpdr, name, path, TRUE);
+ if (error)
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ free(keys);
+
+ for (size_t i = 0; i < size; i++)
+ free(dev_array[i].path);
+
+ return error;
+}
+
+static void drive_hotplug_fsevent_callback(ConstFSEventStreamRef streamRef,
+ void* clientCallBackInfo, size_t numEvents,
+ void* eventPaths,
+ const FSEventStreamEventFlags eventFlags[],
+ const FSEventStreamEventId eventIds[])
+{
+ rdpdrPlugin* rdpdr;
+ UINT error;
+ char** paths = (char**)eventPaths;
+ rdpdr = (rdpdrPlugin*)clientCallBackInfo;
+
+ for (size_t i = 0; i < numEvents; i++)
+ {
+ if (strcmp(paths[i], "/Volumes/") == 0)
+ {
+ if ((error = handle_hotplug(rdpdr)))
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "handle_hotplug failed with error %" PRIu32 "!",
+ error);
+ }
+ else
+ rdpdr_try_send_device_list_announce_request(rdpdr);
+
+ return;
+ }
+ }
+}
+
+static void first_hotplug(rdpdrPlugin* rdpdr)
+{
+ UINT error;
+
+ if ((error = handle_hotplug(rdpdr)))
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "handle_hotplug failed with error %" PRIu32 "!", error);
+ }
+}
+
+static DWORD WINAPI drive_hotplug_thread_func(LPVOID arg)
+{
+ rdpdrPlugin* rdpdr;
+ FSEventStreamRef fsev;
+ rdpdr = (rdpdrPlugin*)arg;
+ CFStringRef path = CFSTR("/Volumes/");
+ CFArrayRef pathsToWatch = CFArrayCreate(kCFAllocatorMalloc, (const void**)&path, 1, NULL);
+ FSEventStreamContext ctx = { 0 };
+
+ ctx.info = arg;
+
+ WINPR_ASSERT(!rdpdr->stopEvent);
+ rdpdr->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!rdpdr->stopEvent)
+ goto out;
+
+ fsev =
+ FSEventStreamCreate(kCFAllocatorMalloc, drive_hotplug_fsevent_callback, &ctx, pathsToWatch,
+ kFSEventStreamEventIdSinceNow, 1, kFSEventStreamCreateFlagNone);
+
+ rdpdr->runLoop = CFRunLoopGetCurrent();
+ FSEventStreamScheduleWithRunLoop(fsev, rdpdr->runLoop, kCFRunLoopDefaultMode);
+ FSEventStreamStart(fsev);
+ CFRunLoopRun();
+ FSEventStreamStop(fsev);
+ FSEventStreamRelease(fsev);
+out:
+ if (rdpdr->stopEvent)
+ {
+ CloseHandle(rdpdr->stopEvent);
+ rdpdr->stopEvent = NULL;
+ }
+ ExitThread(CHANNEL_RC_OK);
+ return CHANNEL_RC_OK;
+}
+
+#else
+
+static const char* automountLocations[] = { "/run/user/%lu/gvfs", "/run/media/%s", "/media/%s",
+ "/media", "/mnt" };
+
+static BOOL isAutomountLocation(const char* path)
+{
+ const size_t nrLocations = sizeof(automountLocations) / sizeof(automountLocations[0]);
+ char buffer[MAX_PATH] = { 0 };
+ uid_t uid = getuid();
+ char uname[MAX_PATH] = { 0 };
+ ULONG size = sizeof(uname) - 1;
+
+ if (!GetUserNameExA(NameSamCompatible, uname, &size))
+ return FALSE;
+
+ if (!path)
+ return FALSE;
+
+ for (size_t x = 0; x < nrLocations; x++)
+ {
+ const char* location = automountLocations[x];
+ size_t length = 0;
+
+ if (strstr(location, "%lu"))
+ snprintf(buffer, sizeof(buffer), location, (unsigned long)uid);
+ else if (strstr(location, "%s"))
+ snprintf(buffer, sizeof(buffer), location, uname);
+ else
+ snprintf(buffer, sizeof(buffer), "%s", location);
+
+ length = strnlen(buffer, sizeof(buffer));
+
+ if (strncmp(buffer, path, length) == 0)
+ {
+ const char* rest = &path[length];
+
+ /* Only consider mount locations with max depth of 1 below the
+ * base path or the base path itself. */
+ if (*rest == '\0')
+ return TRUE;
+ else if (*rest == '/')
+ {
+ const char* token = strstr(&rest[1], "/");
+
+ if (!token || (token[1] == '\0'))
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+#define MAX_USB_DEVICES 100
+
+typedef struct
+{
+ char* path;
+ BOOL to_add;
+} hotplug_dev;
+
+static void handle_mountpoint(hotplug_dev* dev_array, size_t* size, const char* mountpoint)
+{
+ if (!mountpoint)
+ return;
+ /* copy hotpluged device mount point to the dev_array */
+ if (isAutomountLocation(mountpoint) && (*size < MAX_USB_DEVICES))
+ {
+ dev_array[*size].path = _strdup(mountpoint);
+ dev_array[*size].to_add = TRUE;
+ (*size)++;
+ }
+}
+
+#ifdef __sun
+#include <sys/mnttab.h>
+static UINT handle_platform_mounts_sun(wLog* log, hotplug_dev* dev_array, size_t* size)
+{
+ FILE* f;
+ struct mnttab ent;
+ f = winpr_fopen("/etc/mnttab", "r");
+ if (f == NULL)
+ {
+ WLog_Print(log, WLOG_ERROR, "fopen failed!");
+ return ERROR_OPEN_FAILED;
+ }
+ while (getmntent(f, &ent) == 0)
+ {
+ handle_mountpoint(dev_array, size, ent.mnt_mountp);
+ }
+ fclose(f);
+ return ERROR_SUCCESS;
+}
+#endif
+
+#if defined(__FreeBSD__) || defined(__OpenBSD__)
+#include <sys/mount.h>
+static UINT handle_platform_mounts_bsd(wLog* log, hotplug_dev* dev_array, size_t* size)
+{
+ int mntsize;
+ struct statfs* mntbuf = NULL;
+
+ mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
+ if (!mntsize)
+ {
+ /* TODO: handle 'errno' */
+ WLog_Print(log, WLOG_ERROR, "getmntinfo failed!");
+ return ERROR_OPEN_FAILED;
+ }
+ for (size_t idx = 0; idx < (size_t)mntsize; idx++)
+ {
+ handle_mountpoint(dev_array, size, mntbuf[idx].f_mntonname);
+ }
+ free(mntbuf);
+ return ERROR_SUCCESS;
+}
+#endif
+
+#if defined(__LINUX__) || defined(__linux__)
+#include <mntent.h>
+static UINT handle_platform_mounts_linux(wLog* log, hotplug_dev* dev_array, size_t* size)
+{
+ FILE* f = NULL;
+ struct mntent* ent = NULL;
+ f = winpr_fopen("/proc/mounts", "r");
+ if (f == NULL)
+ {
+ WLog_Print(log, WLOG_ERROR, "fopen failed!");
+ return ERROR_OPEN_FAILED;
+ }
+ while ((ent = getmntent(f)) != NULL)
+ {
+ handle_mountpoint(dev_array, size, ent->mnt_dir);
+ }
+ fclose(f);
+ return ERROR_SUCCESS;
+}
+#endif
+
+static UINT handle_platform_mounts(wLog* log, hotplug_dev* dev_array, size_t* size)
+{
+#ifdef __sun
+ return handle_platform_mounts_sun(log, dev_array, size);
+#elif defined(__FreeBSD__) || defined(__OpenBSD__)
+ return handle_platform_mounts_bsd(log, dev_array, size);
+#elif defined(__LINUX__) || defined(__linux__)
+ return handle_platform_mounts_linux(log, dev_array, size);
+#endif
+ return ERROR_CALL_NOT_IMPLEMENTED;
+}
+
+static BOOL device_not_plugged(ULONG_PTR key, void* element, void* data)
+{
+ const WCHAR* path = (const WCHAR*)data;
+ DEVICE_DRIVE_EXT* device_ext = (DEVICE_DRIVE_EXT*)element;
+
+ WINPR_UNUSED(key);
+ WINPR_ASSERT(path);
+
+ if (!device_ext || (device_ext->device.type != RDPDR_DTYP_FILESYSTEM) || !device_ext->path)
+ return TRUE;
+ if (_wcscmp(device_ext->path, path) != 0)
+ return TRUE;
+ return FALSE;
+}
+
+static BOOL device_already_plugged(rdpdrPlugin* rdpdr, const hotplug_dev* device)
+{
+ BOOL rc = FALSE;
+ WCHAR* path = NULL;
+
+ if (!rdpdr || !device)
+ return TRUE;
+ if (!device->to_add)
+ return TRUE;
+
+ WINPR_ASSERT(rdpdr->devman);
+ WINPR_ASSERT(device->path);
+
+ path = ConvertUtf8ToWCharAlloc(device->path, NULL);
+ if (!path)
+ return TRUE;
+
+ rc = device_foreach(rdpdr, TRUE, device_not_plugged, path);
+ free(path);
+ return !rc;
+}
+
+struct hotplug_delete_arg
+{
+ hotplug_dev* dev_array;
+ size_t dev_array_size;
+ rdpdrPlugin* rdpdr;
+};
+
+static BOOL hotplug_delete_foreach(ULONG_PTR key, void* element, void* data)
+{
+ char* path = NULL;
+ BOOL dev_found = FALSE;
+ struct hotplug_delete_arg* arg = (struct hotplug_delete_arg*)data;
+ DEVICE_DRIVE_EXT* device_ext = (DEVICE_DRIVE_EXT*)element;
+
+ WINPR_ASSERT(arg);
+ WINPR_ASSERT(arg->rdpdr);
+ WINPR_ASSERT(arg->dev_array || (arg->dev_array_size == 0));
+
+ if (!device_ext || (device_ext->device.type != RDPDR_DTYP_FILESYSTEM) || !device_ext->path ||
+ !device_ext->automount)
+ return TRUE;
+
+ WINPR_ASSERT(device_ext->path);
+ path = ConvertWCharToUtf8Alloc(device_ext->path, NULL);
+ if (!path)
+ return FALSE;
+
+ /* not plugable device */
+ if (isAutomountLocation(path))
+ {
+ for (size_t i = 0; i < arg->dev_array_size; i++)
+ {
+ hotplug_dev* cur = &arg->dev_array[i];
+ if (cur->path && strstr(path, cur->path) != NULL)
+ {
+ dev_found = TRUE;
+ cur->to_add = FALSE;
+ break;
+ }
+ }
+ }
+
+ free(path);
+
+ if (!dev_found)
+ {
+ UINT error = 0;
+ UINT32 ids[1] = { key };
+
+ WINPR_ASSERT(arg->rdpdr->devman);
+ devman_unregister_device(arg->rdpdr->devman, (void*)key);
+ WINPR_ASSERT(key <= UINT32_MAX);
+
+ error = rdpdr_send_device_list_remove_request(arg->rdpdr, 1, ids);
+ if (error)
+ {
+ WLog_Print(arg->rdpdr->log, WLOG_ERROR,
+ "rdpdr_send_device_list_remove_request failed with error %" PRIu32 "!",
+ error);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static UINT handle_hotplug(rdpdrPlugin* rdpdr)
+{
+ hotplug_dev dev_array[MAX_USB_DEVICES] = { 0 };
+ size_t size = 0;
+ UINT error = ERROR_SUCCESS;
+ struct hotplug_delete_arg arg = { dev_array, ARRAYSIZE(dev_array), rdpdr };
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(rdpdr->devman);
+
+ error = handle_platform_mounts(rdpdr->log, dev_array, &size);
+
+ /* delete removed devices */
+ /* Ignore result */ device_foreach(rdpdr, FALSE, hotplug_delete_foreach, &arg);
+
+ /* add new devices */
+ for (size_t i = 0; i < size; i++)
+ {
+ hotplug_dev* cur = &dev_array[i];
+ if (!device_already_plugged(rdpdr, cur))
+ {
+ const char* path = cur->path;
+ const char* name = strrchr(path, '/') + 1;
+
+ rdpdr_load_drive(rdpdr, name, path, TRUE);
+ error = ERROR_DISK_CHANGE;
+ }
+ }
+
+ for (size_t i = 0; i < size; i++)
+ free(dev_array[i].path);
+
+ return error;
+}
+
+static void first_hotplug(rdpdrPlugin* rdpdr)
+{
+ UINT error = 0;
+
+ WINPR_ASSERT(rdpdr);
+ if ((error = handle_hotplug(rdpdr)))
+ {
+ switch (error)
+ {
+ case ERROR_DISK_CHANGE:
+ case CHANNEL_RC_OK:
+ case ERROR_OPEN_FAILED:
+ case ERROR_CALL_NOT_IMPLEMENTED:
+ break;
+ default:
+ WLog_Print(rdpdr->log, WLOG_ERROR, "handle_hotplug failed with error %" PRIu32 "!",
+ error);
+ break;
+ }
+ }
+}
+
+static DWORD WINAPI drive_hotplug_thread_func(LPVOID arg)
+{
+ rdpdrPlugin* rdpdr = NULL;
+ UINT error = 0;
+ rdpdr = (rdpdrPlugin*)arg;
+
+ WINPR_ASSERT(rdpdr);
+
+ WINPR_ASSERT(!rdpdr->stopEvent);
+ rdpdr->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!rdpdr->stopEvent)
+ goto out;
+
+ while (WaitForSingleObject(rdpdr->stopEvent, 1000) == WAIT_TIMEOUT)
+ {
+ error = handle_hotplug(rdpdr);
+ switch (error)
+ {
+ case ERROR_DISK_CHANGE:
+ rdpdr_try_send_device_list_announce_request(rdpdr);
+ break;
+ case CHANNEL_RC_OK:
+ case ERROR_OPEN_FAILED:
+ case ERROR_CALL_NOT_IMPLEMENTED:
+ break;
+ default:
+ WLog_Print(rdpdr->log, WLOG_ERROR, "handle_hotplug failed with error %" PRIu32 "!",
+ error);
+ goto out;
+ }
+ }
+
+out:
+ error = GetLastError();
+ if (error && rdpdr->rdpcontext)
+ setChannelError(rdpdr->rdpcontext, error, "reported an error");
+
+ if (rdpdr->stopEvent)
+ {
+ CloseHandle(rdpdr->stopEvent);
+ rdpdr->stopEvent = NULL;
+ }
+
+ ExitThread(error);
+ return error;
+}
+
+#endif
+
+#if !defined(_WIN32) && !defined(__IOS__)
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_hotplug_thread_terminate(rdpdrPlugin* rdpdr)
+{
+ UINT error = 0;
+
+ WINPR_ASSERT(rdpdr);
+
+ if (rdpdr->hotplugThread)
+ {
+#if !defined(_WIN32)
+ if (rdpdr->stopEvent)
+ SetEvent(rdpdr->stopEvent);
+#endif
+#ifdef __MACOSX__
+ CFRunLoopStop(rdpdr->runLoop);
+#endif
+
+ if (WaitForSingleObject(rdpdr->hotplugThread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_Print(rdpdr->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ CloseHandle(rdpdr->hotplugThread);
+ rdpdr->hotplugThread = NULL;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+#endif
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_process_connect(rdpdrPlugin* rdpdr)
+{
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(rdpdr);
+
+ rdpdr->devman = devman_new(rdpdr);
+
+ if (!rdpdr->devman)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "devman_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ WINPR_ASSERT(rdpdr->rdpcontext);
+
+ rdpSettings* settings = rdpdr->rdpcontext->settings;
+ WINPR_ASSERT(settings);
+
+ rdpdr->ignoreInvalidDevices = freerdp_settings_get_bool(settings, FreeRDP_IgnoreInvalidDevices);
+
+ const char* name = freerdp_settings_get_string(settings, FreeRDP_ClientHostname);
+ if (!name)
+ name = freerdp_settings_get_string(settings, FreeRDP_ComputerName);
+ strncpy(rdpdr->computerName, name, sizeof(rdpdr->computerName) - 1);
+
+ for (UINT32 index = 0; index < freerdp_settings_get_uint32(settings, FreeRDP_DeviceCount);
+ index++)
+ {
+ const RDPDR_DEVICE* device =
+ freerdp_settings_get_pointer_array(settings, FreeRDP_DeviceArray, index);
+
+ if (device->Type == RDPDR_DTYP_FILESYSTEM)
+ {
+ const char DynamicDrives[] = "DynamicDrives";
+ const RDPDR_DRIVE* drive = (const RDPDR_DRIVE*)device;
+ BOOL hotplugAll = strncmp(drive->Path, "*", 2) == 0;
+ BOOL hotplugLater = strncmp(drive->Path, DynamicDrives, sizeof(DynamicDrives)) == 0;
+
+ if (drive->Path && (hotplugAll || hotplugLater))
+ {
+ if (!rdpdr->async)
+ {
+ WLog_Print(rdpdr->log, WLOG_WARN,
+ "Drive hotplug is not supported in synchronous mode!");
+ continue;
+ }
+
+ if (hotplugAll)
+ first_hotplug(rdpdr);
+
+ /* There might be multiple hotplug related device entries.
+ * Ensure the thread is only started once
+ */
+ if (!rdpdr->hotplugThread)
+ {
+ rdpdr->hotplugThread =
+ CreateThread(NULL, 0, drive_hotplug_thread_func, rdpdr, 0, NULL);
+ if (!rdpdr->hotplugThread)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "CreateThread failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+
+ continue;
+ }
+ }
+
+ if ((error = devman_load_device_service(rdpdr->devman, device, rdpdr->rdpcontext)))
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR,
+ "devman_load_device_service failed with error %" PRIu32 "!", error);
+ return error;
+ }
+ }
+
+ return error;
+}
+
+static UINT rdpdr_process_server_announce_request(rdpdrPlugin* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, rdpdr->serverVersionMajor);
+ Stream_Read_UINT16(s, rdpdr->serverVersionMinor);
+ Stream_Read_UINT32(s, rdpdr->clientID);
+ rdpdr->sequenceId++;
+
+ rdpdr->clientVersionMajor = MIN(RDPDR_VERSION_MAJOR, rdpdr->serverVersionMajor);
+ rdpdr->clientVersionMinor = MIN(RDPDR_VERSION_MINOR_RDP10X, rdpdr->serverVersionMinor);
+ WLog_Print(rdpdr->log, WLOG_DEBUG,
+ "[rdpdr] server announces version %" PRIu32 ".%" PRIu32 ", client uses %" PRIu32
+ ".%" PRIu32,
+ rdpdr->serverVersionMajor, rdpdr->serverVersionMinor, rdpdr->clientVersionMajor,
+ rdpdr->clientVersionMinor);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_send_client_announce_reply(rdpdrPlugin* rdpdr)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(rdpdr->state == RDPDR_CHANNEL_STATE_ANNOUNCE);
+ rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_ANNOUNCE_REPLY);
+
+ s = StreamPool_Take(rdpdr->pool, 12);
+
+ if (!s)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, RDPDR_CTYP_CORE); /* Component (2 bytes) */
+ Stream_Write_UINT16(s, PAKID_CORE_CLIENTID_CONFIRM); /* PacketId (2 bytes) */
+ Stream_Write_UINT16(s, rdpdr->clientVersionMajor);
+ Stream_Write_UINT16(s, rdpdr->clientVersionMinor);
+ Stream_Write_UINT32(s, (UINT32)rdpdr->clientID);
+ return rdpdr_send(rdpdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_send_client_name_request(rdpdrPlugin* rdpdr)
+{
+ wStream* s = NULL;
+ WCHAR* computerNameW = NULL;
+ size_t computerNameLenW = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(rdpdr->state == RDPDR_CHANNEL_STATE_ANNOUNCE_REPLY);
+ rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_NAME_REQUEST);
+
+ if (!rdpdr->computerName[0])
+ {
+ DWORD size = sizeof(rdpdr->computerName) - 1;
+ GetComputerNameA(rdpdr->computerName, &size);
+ }
+
+ WINPR_ASSERT(rdpdr->computerName);
+ computerNameW = ConvertUtf8ToWCharAlloc(rdpdr->computerName, &computerNameLenW);
+ computerNameLenW *= sizeof(WCHAR);
+ WINPR_ASSERT(computerNameLenW >= 0);
+
+ if (computerNameLenW > 0)
+ computerNameLenW += sizeof(WCHAR); // also write '\0'
+
+ s = StreamPool_Take(rdpdr->pool, 16U + computerNameLenW);
+
+ if (!s)
+ {
+ free(computerNameW);
+ WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, RDPDR_CTYP_CORE); /* Component (2 bytes) */
+ Stream_Write_UINT16(s, PAKID_CORE_CLIENT_NAME); /* PacketId (2 bytes) */
+ Stream_Write_UINT32(s, 1); /* unicodeFlag, 0 for ASCII and 1 for Unicode */
+ Stream_Write_UINT32(s, 0); /* codePage, must be set to zero */
+ Stream_Write_UINT32(s,
+ (UINT32)computerNameLenW); /* computerNameLen, including null terminator */
+ Stream_Write(s, computerNameW, (size_t)computerNameLenW);
+ free(computerNameW);
+ return rdpdr_send(rdpdr, s);
+}
+
+static UINT rdpdr_process_server_clientid_confirm(rdpdrPlugin* rdpdr, wStream* s)
+{
+ UINT16 versionMajor = 0;
+ UINT16 versionMinor = 0;
+ UINT32 clientID = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, versionMajor);
+ Stream_Read_UINT16(s, versionMinor);
+ Stream_Read_UINT32(s, clientID);
+
+ if (versionMajor != rdpdr->clientVersionMajor || versionMinor != rdpdr->clientVersionMinor)
+ {
+ WLog_Print(rdpdr->log, WLOG_WARN,
+ "[rdpdr] server announced version %" PRIu32 ".%" PRIu32 ", client uses %" PRIu32
+ ".%" PRIu32 " but clientid confirm requests version %" PRIu32 ".%" PRIu32,
+ rdpdr->serverVersionMajor, rdpdr->serverVersionMinor, rdpdr->clientVersionMajor,
+ rdpdr->clientVersionMinor, versionMajor, versionMinor);
+ rdpdr->clientVersionMajor = versionMajor;
+ rdpdr->clientVersionMinor = versionMinor;
+ }
+
+ if (clientID != rdpdr->clientID)
+ rdpdr->clientID = clientID;
+
+ return CHANNEL_RC_OK;
+}
+
+struct device_announce_arg
+{
+ rdpdrPlugin* rdpdr;
+ wStream* s;
+ BOOL userLoggedOn;
+ UINT32 count;
+};
+
+static BOOL device_announce(ULONG_PTR key, void* element, void* data)
+{
+ struct device_announce_arg* arg = data;
+ rdpdrPlugin* rdpdr = NULL;
+ DEVICE* device = (DEVICE*)element;
+
+ WINPR_UNUSED(key);
+
+ WINPR_ASSERT(arg);
+ WINPR_ASSERT(device);
+ WINPR_ASSERT(arg->rdpdr);
+ WINPR_ASSERT(arg->s);
+
+ rdpdr = arg->rdpdr;
+
+ /**
+ * 1. versionMinor 0x0005 doesn't send PAKID_CORE_USER_LOGGEDON
+ * so all devices should be sent regardless of user_loggedon
+ * 2. smartcard devices should be always sent
+ * 3. other devices are sent only after user_loggedon
+ */
+
+ if ((rdpdr->clientVersionMinor == RDPDR_VERSION_MINOR_RDP51) ||
+ (device->type == RDPDR_DTYP_SMARTCARD) || arg->userLoggedOn)
+ {
+ size_t data_len = (device->data == NULL ? 0 : Stream_GetPosition(device->data));
+
+ if (!Stream_EnsureRemainingCapacity(arg->s, 20 + data_len))
+ {
+ Stream_Release(arg->s);
+ WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!");
+ return FALSE;
+ }
+
+ Stream_Write_UINT32(arg->s, device->type); /* deviceType */
+ Stream_Write_UINT32(arg->s, device->id); /* deviceID */
+ strncpy(Stream_Pointer(arg->s), device->name, 8);
+
+ for (size_t i = 0; i < 8; i++)
+ {
+ BYTE c = 0;
+ Stream_Peek_UINT8(arg->s, c);
+
+ if (c > 0x7F)
+ Stream_Write_UINT8(arg->s, '_');
+ else
+ Stream_Seek_UINT8(arg->s);
+ }
+
+ WINPR_ASSERT(data_len <= UINT32_MAX);
+ Stream_Write_UINT32(arg->s, (UINT32)data_len);
+
+ if (data_len > 0)
+ Stream_Write(arg->s, Stream_Buffer(device->data), data_len);
+
+ arg->count++;
+ WLog_Print(rdpdr->log, WLOG_INFO,
+ "registered [%09s] device #%" PRIu32 ": %s (type=%" PRIu32 " id=%" PRIu32 ")",
+ rdpdr_device_type_string(device->type), arg->count, device->name, device->type,
+ device->id);
+ }
+ return TRUE;
+}
+
+static UINT rdpdr_send_device_list_announce_request(rdpdrPlugin* rdpdr, BOOL userLoggedOn)
+{
+ size_t pos = 0;
+ wStream* s = NULL;
+ size_t count_pos = 0;
+ struct device_announce_arg arg = { 0 };
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(rdpdr->devman);
+
+ if (userLoggedOn)
+ {
+ rdpdr->userLoggedOn = TRUE;
+ }
+
+ s = StreamPool_Take(rdpdr->pool, 256);
+
+ if (!s)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, RDPDR_CTYP_CORE); /* Component (2 bytes) */
+ Stream_Write_UINT16(s, PAKID_CORE_DEVICELIST_ANNOUNCE); /* PacketId (2 bytes) */
+ count_pos = Stream_GetPosition(s);
+ Stream_Seek_UINT32(s); /* deviceCount */
+
+ arg.rdpdr = rdpdr;
+ arg.userLoggedOn = userLoggedOn;
+ arg.s = s;
+ if (!device_foreach(rdpdr, TRUE, device_announce, &arg))
+ return ERROR_INVALID_DATA;
+
+ if (arg.count == 0)
+ {
+ Stream_Release(s);
+ return CHANNEL_RC_OK;
+ }
+ pos = Stream_GetPosition(s);
+ Stream_SetPosition(s, count_pos);
+ Stream_Write_UINT32(s, arg.count);
+ Stream_SetPosition(s, pos);
+ Stream_SealLength(s);
+ return rdpdr_send(rdpdr, s);
+}
+
+UINT rdpdr_try_send_device_list_announce_request(rdpdrPlugin* rdpdr)
+{
+ WINPR_ASSERT(rdpdr);
+ if (rdpdr->state != RDPDR_CHANNEL_STATE_USER_LOGGEDON)
+ {
+ WLog_Print(rdpdr->log, WLOG_DEBUG,
+ "hotplug event received, but channel [RDPDR] is not ready (state %s), ignoring.",
+ rdpdr_state_str(rdpdr->state));
+ return CHANNEL_RC_OK;
+ }
+ return rdpdr_send_device_list_announce_request(rdpdr, TRUE);
+}
+
+static UINT dummy_irp_response(rdpdrPlugin* rdpdr, wStream* s)
+{
+ wStream* output = NULL;
+ UINT32 DeviceId = 0;
+ UINT32 FileId = 0;
+ UINT32 CompletionId = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ output = StreamPool_Take(rdpdr->pool, 256); // RDPDR_DEVICE_IO_RESPONSE_LENGTH
+ if (!output)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_SetPosition(s, 4); /* see "rdpdr_process_receive" */
+
+ Stream_Read_UINT32(s, DeviceId); /* DeviceId (4 bytes) */
+ Stream_Read_UINT32(s, FileId); /* FileId (4 bytes) */
+ Stream_Read_UINT32(s, CompletionId); /* CompletionId (4 bytes) */
+
+ if (!rdpdr_write_iocompletion_header(output, DeviceId, CompletionId,
+ (UINT32)STATUS_UNSUCCESSFUL))
+ return CHANNEL_RC_NO_MEMORY;
+
+ return rdpdr_send(rdpdr, output);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_process_irp(rdpdrPlugin* rdpdr, wStream* s)
+{
+ IRP* irp = NULL;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ irp = irp_new(rdpdr->devman, rdpdr->pool, s, rdpdr->log, &error);
+
+ if (!irp)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "irp_new failed with %" PRIu32 "!", error);
+
+ if (error == CHANNEL_RC_OK || (error == ERROR_DEV_NOT_EXIST && rdpdr->ignoreInvalidDevices))
+ {
+ return dummy_irp_response(rdpdr, s);
+ }
+
+ return error;
+ }
+
+ if (irp->device->IRPRequest)
+ IFCALLRET(irp->device->IRPRequest, error, irp->device, irp);
+ else
+ irp->Discard(irp);
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "device->IRPRequest failed with error %" PRIu32 "",
+ error);
+ irp->Discard(irp);
+ }
+
+ return error;
+}
+
+static UINT rdpdr_process_component(rdpdrPlugin* rdpdr, UINT16 component, UINT16 packetId,
+ wStream* s)
+{
+ UINT32 type = 0;
+ DEVICE* device = NULL;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ switch (component)
+ {
+ case RDPDR_CTYP_PRN:
+ type = RDPDR_DTYP_PRINT;
+ break;
+
+ default:
+ return ERROR_INVALID_DATA;
+ }
+
+ device = devman_get_device_by_type(rdpdr->devman, type);
+
+ if (!device)
+ return ERROR_DEV_NOT_EXIST;
+
+ return IFCALLRESULT(ERROR_INVALID_PARAMETER, device->CustomComponentRequest, device, component,
+ packetId, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static BOOL device_init(ULONG_PTR key, void* element, void* data)
+{
+ wLog* log = data;
+ UINT error = CHANNEL_RC_OK;
+ DEVICE* device = element;
+
+ WINPR_UNUSED(key);
+ WINPR_UNUSED(data);
+
+ IFCALLRET(device->Init, error, device);
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(log, WLOG_ERROR, "Device init failed with %s", WTSErrorToString(error));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static UINT rdpdr_process_init(rdpdrPlugin* rdpdr)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(rdpdr->devman);
+
+ rdpdr->userLoggedOn = FALSE; /* reset possible received state */
+ if (!device_foreach(rdpdr, TRUE, device_init, rdpdr->log))
+ return ERROR_INTERNAL_ERROR;
+ return CHANNEL_RC_OK;
+}
+
+static BOOL state_match(enum RDPDR_CHANNEL_STATE state, size_t count, va_list ap)
+{
+ for (size_t x = 0; x < count; x++)
+ {
+ enum RDPDR_CHANNEL_STATE cur = va_arg(ap, enum RDPDR_CHANNEL_STATE);
+ if (state == cur)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static const char* state_str(size_t count, va_list ap, char* buffer, size_t size)
+{
+ for (size_t x = 0; x < count; x++)
+ {
+ enum RDPDR_CHANNEL_STATE cur = va_arg(ap, enum RDPDR_CHANNEL_STATE);
+ const char* curstr = rdpdr_state_str(cur);
+ winpr_str_append(curstr, buffer, size, "|");
+ }
+ return buffer;
+}
+
+static BOOL rdpdr_state_check(rdpdrPlugin* rdpdr, UINT16 packetid, enum RDPDR_CHANNEL_STATE next,
+ size_t count, ...)
+{
+ va_list ap;
+ WINPR_ASSERT(rdpdr);
+
+ va_start(ap, count);
+ BOOL rc = state_match(rdpdr->state, count, ap);
+ va_end(ap);
+
+ if (!rc)
+ {
+ const char* strstate = rdpdr_state_str(rdpdr->state);
+ char buffer[256] = { 0 };
+
+ va_start(ap, count);
+ state_str(count, ap, buffer, sizeof(buffer));
+ va_end(ap);
+
+ WLog_Print(rdpdr->log, WLOG_ERROR,
+ "channel [RDPDR] received %s, expected states [%s] but have state %s, aborting.",
+ rdpdr_packetid_string(packetid), buffer, strstate);
+
+ rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_INITIAL);
+ return FALSE;
+ }
+ return rdpdr_state_advance(rdpdr, next);
+}
+
+static BOOL rdpdr_check_channel_state(rdpdrPlugin* rdpdr, UINT16 packetid)
+{
+ WINPR_ASSERT(rdpdr);
+
+ switch (packetid)
+ {
+ case PAKID_CORE_SERVER_ANNOUNCE:
+ /* windows servers sometimes send this message.
+ * it seems related to session login (e.g. first initialization for RDP/TLS style login,
+ * then reinitialize the channel after login successful
+ */
+ rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_INITIAL);
+ return rdpdr_state_check(rdpdr, packetid, RDPDR_CHANNEL_STATE_ANNOUNCE, 1,
+ RDPDR_CHANNEL_STATE_INITIAL);
+ case PAKID_CORE_SERVER_CAPABILITY:
+ return rdpdr_state_check(rdpdr, packetid, RDPDR_CHANNEL_STATE_SERVER_CAPS, 6,
+ RDPDR_CHANNEL_STATE_NAME_REQUEST,
+ RDPDR_CHANNEL_STATE_SERVER_CAPS, RDPDR_CHANNEL_STATE_READY,
+ RDPDR_CHANNEL_STATE_CLIENT_CAPS, PAKID_CORE_CLIENTID_CONFIRM,
+ PAKID_CORE_USER_LOGGEDON);
+ case PAKID_CORE_CLIENTID_CONFIRM:
+ return rdpdr_state_check(rdpdr, packetid, RDPDR_CHANNEL_STATE_CLIENTID_CONFIRM, 3,
+ RDPDR_CHANNEL_STATE_CLIENT_CAPS, RDPDR_CHANNEL_STATE_READY,
+ RDPDR_CHANNEL_STATE_USER_LOGGEDON);
+ case PAKID_CORE_USER_LOGGEDON:
+ if (!rdpdr_check_extended_pdu_flag(rdpdr, RDPDR_USER_LOGGEDON_PDU))
+ return FALSE;
+
+ return rdpdr_state_check(
+ rdpdr, packetid, RDPDR_CHANNEL_STATE_USER_LOGGEDON, 4,
+ RDPDR_CHANNEL_STATE_NAME_REQUEST, RDPDR_CHANNEL_STATE_CLIENT_CAPS,
+ RDPDR_CHANNEL_STATE_CLIENTID_CONFIRM, RDPDR_CHANNEL_STATE_READY);
+ default:
+ {
+ enum RDPDR_CHANNEL_STATE state = RDPDR_CHANNEL_STATE_READY;
+ return rdpdr_state_check(rdpdr, packetid, state, 1, state);
+ }
+ }
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_process_receive(rdpdrPlugin* rdpdr, wStream* s)
+{
+ UINT16 component = 0;
+ UINT16 packetId = 0;
+ UINT32 deviceId = 0;
+ UINT32 status = 0;
+ UINT error = ERROR_INVALID_DATA;
+
+ if (!rdpdr || !s)
+ return CHANNEL_RC_NULL_DATA;
+
+ rdpdr_dump_received_packet(rdpdr->log, WLOG_TRACE, s, "[rdpdr-channel] receive");
+ if (Stream_GetRemainingLength(s) >= 4)
+ {
+ Stream_Read_UINT16(s, component); /* Component (2 bytes) */
+ Stream_Read_UINT16(s, packetId); /* PacketId (2 bytes) */
+
+ if (component == RDPDR_CTYP_CORE)
+ {
+ if (!rdpdr_check_channel_state(rdpdr, packetId))
+ return CHANNEL_RC_OK;
+
+ switch (packetId)
+ {
+ case PAKID_CORE_SERVER_ANNOUNCE:
+ if ((error = rdpdr_process_server_announce_request(rdpdr, s)))
+ {
+ }
+ else if ((error = rdpdr_send_client_announce_reply(rdpdr)))
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR,
+ "rdpdr_send_client_announce_reply failed with error %" PRIu32 "",
+ error);
+ }
+ else if ((error = rdpdr_send_client_name_request(rdpdr)))
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR,
+ "rdpdr_send_client_name_request failed with error %" PRIu32 "",
+ error);
+ }
+ else if ((error = rdpdr_process_init(rdpdr)))
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR,
+ "rdpdr_process_init failed with error %" PRIu32 "", error);
+ }
+
+ break;
+
+ case PAKID_CORE_SERVER_CAPABILITY:
+ if ((error = rdpdr_process_capability_request(rdpdr, s)))
+ {
+ }
+ else if ((error = rdpdr_send_capability_response(rdpdr)))
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR,
+ "rdpdr_send_capability_response failed with error %" PRIu32 "",
+ error);
+ }
+
+ break;
+
+ case PAKID_CORE_CLIENTID_CONFIRM:
+ if ((error = rdpdr_process_server_clientid_confirm(rdpdr, s)))
+ {
+ }
+ else if ((error = rdpdr_send_device_list_announce_request(rdpdr, FALSE)))
+ {
+ WLog_Print(
+ rdpdr->log, WLOG_ERROR,
+ "rdpdr_send_device_list_announce_request failed with error %" PRIu32 "",
+ error);
+ }
+ else if (!rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_READY))
+ {
+ error = ERROR_INTERNAL_ERROR;
+ }
+ break;
+
+ case PAKID_CORE_USER_LOGGEDON:
+ if ((error = rdpdr_send_device_list_announce_request(rdpdr, TRUE)))
+ {
+ WLog_Print(
+ rdpdr->log, WLOG_ERROR,
+ "rdpdr_send_device_list_announce_request failed with error %" PRIu32 "",
+ error);
+ }
+ else if (!rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_READY))
+ {
+ error = ERROR_INTERNAL_ERROR;
+ }
+
+ break;
+
+ case PAKID_CORE_DEVICE_REPLY:
+
+ /* connect to a specific resource */
+ if (Stream_GetRemainingLength(s) >= 8)
+ {
+ Stream_Read_UINT32(s, deviceId);
+ Stream_Read_UINT32(s, status);
+
+ if (status != 0)
+ devman_unregister_device(rdpdr->devman, (void*)((size_t)deviceId));
+ error = CHANNEL_RC_OK;
+ }
+
+ break;
+
+ case PAKID_CORE_DEVICE_IOREQUEST:
+ if ((error = rdpdr_process_irp(rdpdr, s)))
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR,
+ "rdpdr_process_irp failed with error %" PRIu32 "", error);
+ return error;
+ }
+ else
+ s = NULL;
+
+ break;
+
+ default:
+ WLog_Print(rdpdr->log, WLOG_ERROR,
+ "RDPDR_CTYP_CORE unknown PacketId: 0x%04" PRIX16 "", packetId);
+ error = ERROR_INVALID_DATA;
+ break;
+ }
+ }
+ else
+ {
+ error = rdpdr_process_component(rdpdr, component, packetId, s);
+
+ if (error != CHANNEL_RC_OK)
+ {
+ DWORD level = WLOG_ERROR;
+ if (rdpdr->ignoreInvalidDevices)
+ {
+ if (error == ERROR_DEV_NOT_EXIST)
+ {
+ level = WLOG_WARN;
+ error = CHANNEL_RC_OK;
+ }
+ }
+ WLog_Print(rdpdr->log, level,
+ "Unknown message: Component: %s [0x%04" PRIX16
+ "] PacketId: %s [0x%04" PRIX16 "]",
+ rdpdr_component_string(component), component,
+ rdpdr_packetid_string(packetId), packetId);
+ }
+ }
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpdr_send(rdpdrPlugin* rdpdr, wStream* s)
+{
+ UINT status = 0;
+ rdpdrPlugin* plugin = (rdpdrPlugin*)rdpdr;
+
+ if (!rdpdr || !s)
+ {
+ Stream_Release(s);
+ return CHANNEL_RC_NULL_DATA;
+ }
+
+ if (!plugin)
+ {
+ Stream_Release(s);
+ status = CHANNEL_RC_BAD_INIT_HANDLE;
+ }
+ else
+ {
+ const size_t pos = Stream_GetPosition(s);
+ rdpdr_dump_send_packet(rdpdr->log, WLOG_TRACE, s, "[rdpdr-channel] send");
+ status = plugin->channelEntryPoints.pVirtualChannelWriteEx(
+ plugin->InitHandle, plugin->OpenHandle, Stream_Buffer(s), pos, s);
+ }
+
+ if (status != CHANNEL_RC_OK)
+ {
+ Stream_Release(s);
+ WLog_Print(rdpdr->log, WLOG_ERROR, "pVirtualChannelWriteEx failed with %s [%08" PRIX32 "]",
+ WTSErrorToString(status), status);
+ }
+
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_virtual_channel_event_data_received(rdpdrPlugin* rdpdr, void* pData,
+ UINT32 dataLength, UINT32 totalLength,
+ UINT32 dataFlags)
+{
+ wStream* data_in = NULL;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(pData || (dataLength == 0));
+
+ if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME))
+ {
+ /*
+ * According to MS-RDPBCGR 2.2.6.1, "All virtual channel traffic MUST be suspended.
+ * This flag is only valid in server-to-client virtual channel traffic. It MUST be
+ * ignored in client-to-server data." Thus it would be best practice to cease data
+ * transmission. However, simply returning here avoids a crash.
+ */
+ return CHANNEL_RC_OK;
+ }
+
+ if (dataFlags & CHANNEL_FLAG_FIRST)
+ {
+ if (rdpdr->data_in != NULL)
+ Stream_Release(rdpdr->data_in);
+
+ rdpdr->data_in = StreamPool_Take(rdpdr->pool, totalLength);
+
+ if (!rdpdr->data_in)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+
+ data_in = rdpdr->data_in;
+
+ if (!Stream_EnsureRemainingCapacity(data_in, dataLength))
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!");
+ return ERROR_INVALID_DATA;
+ }
+
+ Stream_Write(data_in, pData, dataLength);
+
+ if (dataFlags & CHANNEL_FLAG_LAST)
+ {
+ const size_t pos = Stream_GetPosition(data_in);
+ const size_t cap = Stream_Capacity(data_in);
+ if (cap < pos)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR,
+ "rdpdr_virtual_channel_event_data_received: read error");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_SealLength(data_in);
+ Stream_SetPosition(data_in, 0);
+
+ if (rdpdr->async)
+ {
+ if (!MessageQueue_Post(rdpdr->queue, NULL, 0, (void*)data_in, NULL))
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "MessageQueue_Post failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+ rdpdr->data_in = NULL;
+ }
+ else
+ {
+ UINT error = rdpdr_process_receive(rdpdr, data_in);
+ Stream_Release(data_in);
+ rdpdr->data_in = NULL;
+ if (error)
+ return error;
+ }
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static VOID VCAPITYPE rdpdr_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle,
+ UINT event, LPVOID pData,
+ UINT32 dataLength, UINT32 totalLength,
+ UINT32 dataFlags)
+{
+ UINT error = CHANNEL_RC_OK;
+ rdpdrPlugin* rdpdr = (rdpdrPlugin*)lpUserParam;
+
+ WINPR_ASSERT(rdpdr);
+ switch (event)
+ {
+ case CHANNEL_EVENT_DATA_RECEIVED:
+ if (!rdpdr || !pData || (rdpdr->OpenHandle != openHandle))
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "error no match");
+ return;
+ }
+ if ((error = rdpdr_virtual_channel_event_data_received(rdpdr, pData, dataLength,
+ totalLength, dataFlags)))
+ WLog_Print(rdpdr->log, WLOG_ERROR,
+ "rdpdr_virtual_channel_event_data_received failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_WRITE_CANCELLED:
+ case CHANNEL_EVENT_WRITE_COMPLETE:
+ {
+ wStream* s = (wStream*)pData;
+ Stream_Release(s);
+ }
+ break;
+
+ case CHANNEL_EVENT_USER:
+ break;
+ }
+
+ if (error && rdpdr && rdpdr->rdpcontext)
+ setChannelError(rdpdr->rdpcontext, error,
+ "rdpdr_virtual_channel_open_event_ex reported an error");
+
+ return;
+}
+
+static DWORD WINAPI rdpdr_virtual_channel_client_thread(LPVOID arg)
+{
+ rdpdrPlugin* rdpdr = (rdpdrPlugin*)arg;
+ UINT error = 0;
+
+ if (!rdpdr)
+ {
+ ExitThread((DWORD)CHANNEL_RC_NULL_DATA);
+ return CHANNEL_RC_NULL_DATA;
+ }
+
+ if ((error = rdpdr_process_connect(rdpdr)))
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "rdpdr_process_connect failed with error %" PRIu32 "!",
+ error);
+
+ if (rdpdr->rdpcontext)
+ setChannelError(rdpdr->rdpcontext, error,
+ "rdpdr_virtual_channel_client_thread reported an error");
+
+ ExitThread(error);
+ return error;
+ }
+
+ while (1)
+ {
+ wMessage message = { 0 };
+ WINPR_ASSERT(rdpdr);
+
+ if (!MessageQueue_Wait(rdpdr->queue))
+ break;
+
+ if (MessageQueue_Peek(rdpdr->queue, &message, TRUE))
+ {
+ if (message.id == WMQ_QUIT)
+ break;
+
+ if (message.id == 0)
+ {
+ wStream* data = (wStream*)message.wParam;
+
+ error = rdpdr_process_receive(rdpdr, data);
+
+ Stream_Release(data);
+ if (error)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR,
+ "rdpdr_process_receive failed with error %" PRIu32 "!", error);
+
+ if (rdpdr->rdpcontext)
+ setChannelError(rdpdr->rdpcontext, error,
+ "rdpdr_virtual_channel_client_thread reported an error");
+
+ goto fail;
+ }
+ }
+ }
+ }
+
+fail:
+ if ((error = drive_hotplug_thread_terminate(rdpdr)))
+ WLog_Print(rdpdr->log, WLOG_ERROR,
+ "drive_hotplug_thread_terminate failed with error %" PRIu32 "!", error);
+
+ ExitThread(error);
+ return error;
+}
+
+static void queue_free(void* obj)
+{
+ wStream* s = NULL;
+ wMessage* msg = (wMessage*)obj;
+
+ if (!msg || (msg->id != 0))
+ return;
+
+ s = (wStream*)msg->wParam;
+ WINPR_ASSERT(s);
+ Stream_Release(s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_virtual_channel_event_connected(rdpdrPlugin* rdpdr, LPVOID pData,
+ UINT32 dataLength)
+{
+ wObject* obj = NULL;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_UNUSED(pData);
+ WINPR_UNUSED(dataLength);
+
+ if (rdpdr->async)
+ {
+ rdpdr->queue = MessageQueue_New(NULL);
+
+ if (!rdpdr->queue)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "MessageQueue_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ obj = MessageQueue_Object(rdpdr->queue);
+ obj->fnObjectFree = queue_free;
+
+ if (!(rdpdr->thread = CreateThread(NULL, 0, rdpdr_virtual_channel_client_thread,
+ (void*)rdpdr, 0, NULL)))
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "CreateThread failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+ else
+ {
+ UINT error = rdpdr_process_connect(rdpdr);
+ if (error)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR,
+ "rdpdr_process_connect failed with error %" PRIu32 "!", error);
+ return error;
+ }
+ }
+
+ return rdpdr->channelEntryPoints.pVirtualChannelOpenEx(rdpdr->InitHandle, &rdpdr->OpenHandle,
+ rdpdr->channelDef.name,
+ rdpdr_virtual_channel_open_event_ex);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_virtual_channel_event_disconnected(rdpdrPlugin* rdpdr)
+{
+ UINT error = 0;
+
+ WINPR_ASSERT(rdpdr);
+
+ if (rdpdr->OpenHandle == 0)
+ return CHANNEL_RC_OK;
+
+ if (rdpdr->queue && rdpdr->thread)
+ {
+ if (MessageQueue_PostQuit(rdpdr->queue, 0) &&
+ (WaitForSingleObject(rdpdr->thread, INFINITE) == WAIT_FAILED))
+ {
+ error = GetLastError();
+ WLog_Print(rdpdr->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+ }
+
+ if (rdpdr->thread)
+ CloseHandle(rdpdr->thread);
+ MessageQueue_Free(rdpdr->queue);
+ rdpdr->queue = NULL;
+ rdpdr->thread = NULL;
+
+ WINPR_ASSERT(rdpdr->channelEntryPoints.pVirtualChannelCloseEx);
+ error = rdpdr->channelEntryPoints.pVirtualChannelCloseEx(rdpdr->InitHandle, rdpdr->OpenHandle);
+
+ if (CHANNEL_RC_OK != error)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "pVirtualChannelCloseEx failed with %s [%08" PRIX32 "]",
+ WTSErrorToString(error), error);
+ }
+
+ rdpdr->OpenHandle = 0;
+
+ if (rdpdr->data_in)
+ {
+ Stream_Release(rdpdr->data_in);
+ rdpdr->data_in = NULL;
+ }
+
+ if (rdpdr->devman)
+ {
+ devman_free(rdpdr->devman);
+ rdpdr->devman = NULL;
+ }
+
+ return error;
+}
+
+static void rdpdr_virtual_channel_event_terminated(rdpdrPlugin* rdpdr)
+{
+ WINPR_ASSERT(rdpdr);
+ rdpdr->InitHandle = 0;
+ StreamPool_Free(rdpdr->pool);
+ free(rdpdr);
+}
+
+static VOID VCAPITYPE rdpdr_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle,
+ UINT event, LPVOID pData, UINT dataLength)
+{
+ UINT error = CHANNEL_RC_OK;
+ rdpdrPlugin* rdpdr = (rdpdrPlugin*)lpUserParam;
+
+ if (!rdpdr || (rdpdr->InitHandle != pInitHandle))
+ {
+ WLog_ERR(TAG, "error no match");
+ return;
+ }
+
+ WINPR_ASSERT(pData || (dataLength == 0));
+
+ switch (event)
+ {
+ case CHANNEL_EVENT_INITIALIZED:
+ break;
+
+ case CHANNEL_EVENT_CONNECTED:
+ if ((error = rdpdr_virtual_channel_event_connected(rdpdr, pData, dataLength)))
+ WLog_Print(rdpdr->log, WLOG_ERROR,
+ "rdpdr_virtual_channel_event_connected failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_DISCONNECTED:
+ if ((error = rdpdr_virtual_channel_event_disconnected(rdpdr)))
+ WLog_Print(rdpdr->log, WLOG_ERROR,
+ "rdpdr_virtual_channel_event_disconnected failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_TERMINATED:
+ rdpdr_virtual_channel_event_terminated(rdpdr);
+ rdpdr = NULL;
+ break;
+
+ case CHANNEL_EVENT_ATTACHED:
+ case CHANNEL_EVENT_DETACHED:
+ default:
+ WLog_Print(rdpdr->log, WLOG_ERROR, "unknown event %" PRIu32 "!", event);
+ break;
+ }
+
+ if (error && rdpdr && rdpdr->rdpcontext)
+ setChannelError(rdpdr->rdpcontext, error,
+ "rdpdr_virtual_channel_init_event_ex reported an error");
+}
+
+/* rdpdr is always built-in */
+#define VirtualChannelEntryEx rdpdr_VirtualChannelEntryEx
+
+FREERDP_ENTRY_POINT(BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS pEntryPoints,
+ PVOID pInitHandle))
+{
+ UINT rc = 0;
+ rdpdrPlugin* rdpdr = NULL;
+ CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx = NULL;
+
+ WINPR_ASSERT(pEntryPoints);
+ WINPR_ASSERT(pInitHandle);
+
+ rdpdr = (rdpdrPlugin*)calloc(1, sizeof(rdpdrPlugin));
+
+ if (!rdpdr)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return FALSE;
+ }
+ rdpdr->log = WLog_Get(TAG);
+
+ rdpdr->clientExtendedPDU =
+ RDPDR_DEVICE_REMOVE_PDUS | RDPDR_CLIENT_DISPLAY_NAME_PDU | RDPDR_USER_LOGGEDON_PDU;
+ rdpdr->clientIOCode1 =
+ RDPDR_IRP_MJ_CREATE | RDPDR_IRP_MJ_CLEANUP | RDPDR_IRP_MJ_CLOSE | RDPDR_IRP_MJ_READ |
+ RDPDR_IRP_MJ_WRITE | RDPDR_IRP_MJ_FLUSH_BUFFERS | RDPDR_IRP_MJ_SHUTDOWN |
+ RDPDR_IRP_MJ_DEVICE_CONTROL | RDPDR_IRP_MJ_QUERY_VOLUME_INFORMATION |
+ RDPDR_IRP_MJ_SET_VOLUME_INFORMATION | RDPDR_IRP_MJ_QUERY_INFORMATION |
+ RDPDR_IRP_MJ_SET_INFORMATION | RDPDR_IRP_MJ_DIRECTORY_CONTROL | RDPDR_IRP_MJ_LOCK_CONTROL |
+ RDPDR_IRP_MJ_QUERY_SECURITY | RDPDR_IRP_MJ_SET_SECURITY;
+
+ rdpdr->clientExtraFlags1 = ENABLE_ASYNCIO;
+
+ rdpdr->pool = StreamPool_New(TRUE, 1024);
+ if (!rdpdr->pool)
+ {
+ free(rdpdr);
+ return FALSE;
+ }
+
+ rdpdr->channelDef.options =
+ CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP | CHANNEL_OPTION_COMPRESS_RDP;
+ sprintf_s(rdpdr->channelDef.name, ARRAYSIZE(rdpdr->channelDef.name), RDPDR_SVC_CHANNEL_NAME);
+ rdpdr->sequenceId = 0;
+ pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints;
+
+ if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) &&
+ (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER))
+ {
+ rdpdr->rdpcontext = pEntryPointsEx->context;
+ if (!freerdp_settings_get_bool(rdpdr->rdpcontext->settings,
+ FreeRDP_SynchronousStaticChannels))
+ rdpdr->async = TRUE;
+ }
+
+ CopyMemory(&(rdpdr->channelEntryPoints), pEntryPoints, sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX));
+ rdpdr->InitHandle = pInitHandle;
+ rc = rdpdr->channelEntryPoints.pVirtualChannelInitEx(
+ rdpdr, NULL, pInitHandle, &rdpdr->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000,
+ rdpdr_virtual_channel_init_event_ex);
+
+ if (CHANNEL_RC_OK != rc)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "pVirtualChannelInitEx failed with %s [%08" PRIX32 "]",
+ WTSErrorToString(rc), rc);
+ free(rdpdr);
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/channels/rdpdr/client/rdpdr_main.h b/channels/rdpdr/client/rdpdr_main.h
new file mode 100644
index 0000000..91131cb
--- /dev/null
+++ b/channels/rdpdr/client/rdpdr_main.h
@@ -0,0 +1,121 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Device Redirection Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 Inuvika Inc.
+ * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPDR_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_RDPDR_CLIENT_MAIN_H
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+
+#include <freerdp/api.h>
+#include <freerdp/svc.h>
+#include <freerdp/addin.h>
+
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/channels/log.h>
+
+#ifdef __MACOSX__
+#include <CoreServices/CoreServices.h>
+#endif
+
+enum RDPDR_CHANNEL_STATE
+{
+ RDPDR_CHANNEL_STATE_INITIAL = 0,
+ RDPDR_CHANNEL_STATE_ANNOUNCE,
+ RDPDR_CHANNEL_STATE_ANNOUNCE_REPLY,
+ RDPDR_CHANNEL_STATE_NAME_REQUEST,
+ RDPDR_CHANNEL_STATE_SERVER_CAPS,
+ RDPDR_CHANNEL_STATE_CLIENT_CAPS,
+ RDPDR_CHANNEL_STATE_CLIENTID_CONFIRM,
+ RDPDR_CHANNEL_STATE_READY,
+ RDPDR_CHANNEL_STATE_USER_LOGGEDON
+};
+
+typedef struct
+{
+ CHANNEL_DEF channelDef;
+ CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints;
+
+ enum RDPDR_CHANNEL_STATE state;
+ HANDLE thread;
+ wStream* data_in;
+ void* InitHandle;
+ DWORD OpenHandle;
+ wMessageQueue* queue;
+
+ DEVMAN* devman;
+ BOOL ignoreInvalidDevices;
+
+ UINT32 serverOsType;
+ UINT32 serverOsVersion;
+ UINT16 serverVersionMajor;
+ UINT16 serverVersionMinor;
+ UINT32 serverExtendedPDU;
+ UINT32 serverIOCode1;
+ UINT32 serverIOCode2;
+ UINT32 serverExtraFlags1;
+ UINT32 serverExtraFlags2;
+ UINT32 serverSpecialTypeDeviceCap;
+
+ UINT32 clientOsType;
+ UINT32 clientOsVersion;
+ UINT16 clientVersionMajor;
+ UINT16 clientVersionMinor;
+ UINT32 clientExtendedPDU;
+ UINT32 clientIOCode1;
+ UINT32 clientIOCode2;
+ UINT32 clientExtraFlags1;
+ UINT32 clientExtraFlags2;
+ UINT32 clientSpecialTypeDeviceCap;
+
+ UINT32 clientID;
+ char computerName[256];
+
+ UINT32 sequenceId;
+ BOOL userLoggedOn;
+
+ /* hotplug support */
+ HANDLE hotplugThread;
+#ifdef _WIN32
+ HWND hotplug_wnd;
+#endif
+#ifdef __MACOSX__
+ CFRunLoopRef runLoop;
+#endif
+#ifndef _WIN32
+ HANDLE stopEvent;
+#endif
+ rdpContext* rdpcontext;
+ wStreamPool* pool;
+ wLog* log;
+ BOOL async;
+} rdpdrPlugin;
+
+BOOL rdpdr_state_advance(rdpdrPlugin* rdpdr, enum RDPDR_CHANNEL_STATE next);
+UINT rdpdr_send(rdpdrPlugin* rdpdr, wStream* s);
+
+#endif /* FREERDP_CHANNEL_RDPDR_CLIENT_MAIN_H */
diff --git a/channels/rdpdr/server/CMakeLists.txt b/channels/rdpdr/server/CMakeLists.txt
new file mode 100644
index 0000000..7d1f7e5
--- /dev/null
+++ b/channels/rdpdr/server/CMakeLists.txt
@@ -0,0 +1,29 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_server("rdpdr")
+
+set(${MODULE_PREFIX}_SRCS
+ rdpdr_main.c
+ rdpdr_main.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry")
diff --git a/channels/rdpdr/server/rdpdr_main.c b/channels/rdpdr/server/rdpdr_main.c
new file mode 100644
index 0000000..bad6e23
--- /dev/null
+++ b/channels/rdpdr/server/rdpdr_main.c
@@ -0,0 +1,3574 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Device Redirection Virtual Channel Extension
+ *
+ * Copyright 2014 Dell Software <Mike.McDonald@software.dell.com>
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015-2022 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2022 Armin Novak <anovak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/utils/rdpdr_utils.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/nt.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+
+#include <freerdp/channels/log.h>
+#include "rdpdr_main.h"
+
+#define RDPDR_ADD_PRINTER_EVENT 0x00000001
+#define RDPDR_UPDATE_PRINTER_EVENT 0x00000002
+#define RDPDR_DELETE_PRINTER_EVENT 0x00000003
+#define RDPDR_RENAME_PRINTER_EVENT 0x00000004
+
+#define RDPDR_HEADER_LENGTH 4
+#define RDPDR_CAPABILITY_HEADER_LENGTH 8
+
+struct s_rdpdr_server_private
+{
+ HANDLE Thread;
+ HANDLE StopEvent;
+ void* ChannelHandle;
+
+ UINT32 ClientId;
+ UINT16 VersionMajor;
+ UINT16 VersionMinor;
+ char* ClientComputerName;
+
+ BOOL UserLoggedOnPdu;
+
+ wListDictionary* IrpList;
+ UINT32 NextCompletionId;
+
+ wHashTable* devicelist;
+ wLog* log;
+};
+
+static void rdpdr_device_free(RdpdrDevice* device)
+{
+ if (!device)
+ return;
+ free(device->DeviceData);
+ free(device);
+}
+
+static void rdpdr_device_free_h(void* obj)
+{
+ RdpdrDevice* other = obj;
+ rdpdr_device_free(other);
+}
+
+static UINT32 rdpdr_deviceid_hash(const void* id)
+{
+ WINPR_ASSERT(id);
+ return *((const UINT32*)id);
+}
+
+static RdpdrDevice* rdpdr_device_new(void)
+{
+ return calloc(1, sizeof(RdpdrDevice));
+}
+
+static void* rdpdr_device_clone(const void* val)
+{
+ const RdpdrDevice* other = val;
+
+ if (!other)
+ return NULL;
+
+ RdpdrDevice* tmp = rdpdr_device_new();
+ if (!tmp)
+ goto fail;
+
+ *tmp = *other;
+ if (other->DeviceData)
+ {
+ tmp->DeviceData = malloc(other->DeviceDataLength);
+ if (!tmp->DeviceData)
+ goto fail;
+ memcpy(tmp->DeviceData, other->DeviceData, other->DeviceDataLength);
+ }
+ return tmp;
+
+fail:
+ rdpdr_device_free(tmp);
+ return NULL;
+}
+
+static RdpdrDevice* rdpdr_get_device_by_id(RdpdrServerPrivate* priv, UINT32 DeviceId)
+{
+ WINPR_ASSERT(priv);
+
+ return HashTable_GetItemValue(priv->devicelist, &DeviceId);
+}
+
+static BOOL rdpdr_remove_device_by_id(RdpdrServerPrivate* priv, UINT32 DeviceId)
+{
+ const RdpdrDevice* device = rdpdr_get_device_by_id(priv, DeviceId);
+ WINPR_ASSERT(priv);
+
+ if (!device)
+ {
+ WLog_Print(priv->log, WLOG_WARN, "[del] Device Id: 0x%08" PRIX32 ": no such device",
+ DeviceId);
+ return FALSE;
+ }
+ WLog_Print(priv->log, WLOG_DEBUG,
+ "[del] Device Name: %s Id: 0x%08" PRIX32 " DataLength: %" PRIu32 "",
+ device->PreferredDosName, device->DeviceId, device->DeviceDataLength);
+ return HashTable_Remove(priv->devicelist, &DeviceId);
+}
+
+static BOOL rdpdr_add_device(RdpdrServerPrivate* priv, const RdpdrDevice* device)
+{
+ WINPR_ASSERT(priv);
+ WINPR_ASSERT(device);
+
+ WLog_Print(priv->log, WLOG_DEBUG,
+ "[add] Device Name: %s Id: 0x%08" PRIX32 " DataLength: %" PRIu32 "",
+ device->PreferredDosName, device->DeviceId, device->DeviceDataLength);
+
+ return HashTable_Insert(priv->devicelist, &device->DeviceId, device);
+}
+
+static UINT32 g_ClientId = 0;
+
+static const WCHAR* rdpdr_read_ustring(wLog* log, wStream* s, size_t bytelen)
+{
+ const size_t charlen = (bytelen + 1) / sizeof(WCHAR);
+ const WCHAR* str = Stream_ConstPointer(s);
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, bytelen))
+ return NULL;
+ if (_wcsnlen(str, charlen) == charlen)
+ {
+ WLog_Print(log, WLOG_WARN, "[rdpdr] unicode string not '\0' terminated");
+ return NULL;
+ }
+ Stream_Seek(s, bytelen);
+ return str;
+}
+
+static RDPDR_IRP* rdpdr_server_irp_new(void)
+{
+ RDPDR_IRP* irp = (RDPDR_IRP*)calloc(1, sizeof(RDPDR_IRP));
+ return irp;
+}
+
+static void rdpdr_server_irp_free(RDPDR_IRP* irp)
+{
+ free(irp);
+}
+
+static BOOL rdpdr_server_enqueue_irp(RdpdrServerContext* context, RDPDR_IRP* irp)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ const uintptr_t key = irp->CompletionId + 1ull;
+ return ListDictionary_Add(context->priv->IrpList, (void*)key, irp);
+}
+
+static RDPDR_IRP* rdpdr_server_dequeue_irp(RdpdrServerContext* context, UINT32 completionId)
+{
+ RDPDR_IRP* irp = NULL;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ const uintptr_t key = completionId + 1ull;
+ irp = (RDPDR_IRP*)ListDictionary_Take(context->priv->IrpList, (void*)key);
+ return irp;
+}
+
+static UINT rdpdr_seal_send_free_request(RdpdrServerContext* context, wStream* s)
+{
+ BOOL status = 0;
+ size_t length = 0;
+ ULONG written = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(s);
+
+ Stream_SealLength(s);
+ length = Stream_Length(s);
+ WINPR_ASSERT(length <= ULONG_MAX);
+ Stream_SetPosition(s, 0);
+
+ if (length >= RDPDR_HEADER_LENGTH)
+ {
+ RDPDR_HEADER header = { 0 };
+ Stream_Read_UINT16(s, header.Component);
+ Stream_Read_UINT16(s, header.PacketId);
+
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "sending message {Component %s[%04" PRIx16 "], PacketId %s[%04" PRIx16 "]",
+ rdpdr_component_string(header.Component), header.Component,
+ rdpdr_packetid_string(header.PacketId), header.PacketId);
+ }
+ winpr_HexLogDump(context->priv->log, WLOG_DEBUG, Stream_Buffer(s), Stream_Length(s));
+ status = WTSVirtualChannelWrite(context->priv->ChannelHandle, (PCHAR)Stream_Buffer(s),
+ (ULONG)length, &written);
+ Stream_Free(s, TRUE);
+ return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_send_announce_request(RdpdrServerContext* context)
+{
+ UINT error = 0;
+ wStream* s = NULL;
+ RDPDR_HEADER header = { 0 };
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ header.Component = RDPDR_CTYP_CORE;
+ header.PacketId = PAKID_CORE_SERVER_ANNOUNCE;
+
+ error = IFCALLRESULT(CHANNEL_RC_OK, context->SendServerAnnounce, context);
+ if (error != CHANNEL_RC_OK)
+ return error;
+
+ s = Stream_New(NULL, RDPDR_HEADER_LENGTH + 8);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, header.Component); /* Component (2 bytes) */
+ Stream_Write_UINT16(s, header.PacketId); /* PacketId (2 bytes) */
+ Stream_Write_UINT16(s, context->priv->VersionMajor); /* VersionMajor (2 bytes) */
+ Stream_Write_UINT16(s, context->priv->VersionMinor); /* VersionMinor (2 bytes) */
+ Stream_Write_UINT32(s, context->priv->ClientId); /* ClientId (4 bytes) */
+ return rdpdr_seal_send_free_request(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_receive_announce_response(RdpdrServerContext* context, wStream* s,
+ const RDPDR_HEADER* header)
+{
+ UINT32 ClientId = 0;
+ UINT16 VersionMajor = 0;
+ UINT16 VersionMinor = 0;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(header);
+
+ WINPR_UNUSED(header);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, VersionMajor); /* VersionMajor (2 bytes) */
+ Stream_Read_UINT16(s, VersionMinor); /* VersionMinor (2 bytes) */
+ Stream_Read_UINT32(s, ClientId); /* ClientId (4 bytes) */
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "Client Announce Response: VersionMajor: 0x%08" PRIX16 " VersionMinor: 0x%04" PRIX16
+ " ClientId: 0x%08" PRIX32 "",
+ VersionMajor, VersionMinor, ClientId);
+ context->priv->ClientId = ClientId;
+
+ return IFCALLRESULT(CHANNEL_RC_OK, context->ReceiveAnnounceResponse, context, VersionMajor,
+ VersionMinor, ClientId);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_receive_client_name_request(RdpdrServerContext* context, wStream* s,
+ const RDPDR_HEADER* header)
+{
+ UINT32 UnicodeFlag = 0;
+ UINT32 CodePage = 0;
+ UINT32 ComputerNameLen = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+ WINPR_UNUSED(header);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, UnicodeFlag); /* UnicodeFlag (4 bytes) */
+ Stream_Read_UINT32(s, CodePage); /* CodePage (4 bytes), MUST be set to zero */
+ Stream_Read_UINT32(s, ComputerNameLen); /* ComputerNameLen (4 bytes) */
+ /* UnicodeFlag is either 0 or 1, the other 31 bits must be ignored.
+ */
+ UnicodeFlag = UnicodeFlag & 0x00000001;
+
+ if (CodePage != 0)
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[MS-RDPEFS] 2.2.2.4 Client Name Request (DR_CORE_CLIENT_NAME_REQ)::CodePage "
+ "must be 0, but is 0x%08" PRIx32,
+ CodePage);
+
+ /**
+ * Caution: ComputerNameLen is given *bytes*,
+ * not in characters, including the NULL terminator!
+ */
+
+ if (UnicodeFlag)
+ {
+ if ((ComputerNameLen % 2) || ComputerNameLen > 512 || ComputerNameLen < 2)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "invalid unicode computer name length: %" PRIu32 "", ComputerNameLen);
+ return ERROR_INVALID_DATA;
+ }
+ }
+ else
+ {
+ if (ComputerNameLen > 256 || ComputerNameLen < 1)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "invalid ascii computer name length: %" PRIu32 "", ComputerNameLen);
+ return ERROR_INVALID_DATA;
+ }
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, ComputerNameLen))
+ return ERROR_INVALID_DATA;
+
+ /* ComputerName must be null terminated, check if it really is */
+ const char* computerName = Stream_ConstPointer(s);
+ if (computerName[ComputerNameLen - 1] || (UnicodeFlag && computerName[ComputerNameLen - 2]))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "computer name must be null terminated");
+ return ERROR_INVALID_DATA;
+ }
+
+ if (context->priv->ClientComputerName)
+ {
+ free(context->priv->ClientComputerName);
+ context->priv->ClientComputerName = NULL;
+ }
+
+ if (UnicodeFlag)
+ {
+ context->priv->ClientComputerName =
+ Stream_Read_UTF16_String_As_UTF8(s, ComputerNameLen / sizeof(WCHAR), NULL);
+ if (!context->priv->ClientComputerName)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "failed to convert client computer name");
+ return ERROR_INVALID_DATA;
+ }
+ }
+ else
+ {
+ const char* name = Stream_ConstPointer(s);
+ context->priv->ClientComputerName = _strdup(name);
+ Stream_Seek(s, ComputerNameLen);
+
+ if (!context->priv->ClientComputerName)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "failed to duplicate client computer name");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+
+ WLog_Print(context->priv->log, WLOG_DEBUG, "ClientComputerName: %s",
+ context->priv->ClientComputerName);
+ return IFCALLRESULT(CHANNEL_RC_OK, context->ReceiveClientNameRequest, context, ComputerNameLen,
+ context->priv->ClientComputerName);
+}
+
+static UINT rdpdr_server_write_capability_set_header_cb(RdpdrServerContext* context, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ UINT error = rdpdr_write_capset_header(context->priv->log, s, header);
+ if (error != CHANNEL_RC_OK)
+ return error;
+
+ return IFCALLRESULT(CHANNEL_RC_OK, context->SendCaps, context, header, 0, NULL);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_read_general_capability_set(RdpdrServerContext* context, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ UINT32 ioCode1 = 0;
+ UINT32 extraFlags1 = 0;
+ UINT32 extendedPdu = 0;
+ UINT16 VersionMajor = 0;
+ UINT16 VersionMinor = 0;
+ UINT32 SpecialTypeDeviceCap = 0;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(header);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Seek_UINT32(s); /* osType (4 bytes), ignored on receipt */
+ Stream_Seek_UINT32(s); /* osVersion (4 bytes), unused and must be set to zero */
+ Stream_Read_UINT16(s, VersionMajor); /* protocolMajorVersion (2 bytes) */
+ Stream_Read_UINT16(s, VersionMinor); /* protocolMinorVersion (2 bytes) */
+ Stream_Read_UINT32(s, ioCode1); /* ioCode1 (4 bytes) */
+ Stream_Seek_UINT32(s); /* ioCode2 (4 bytes), must be set to zero, reserved for future use */
+ Stream_Read_UINT32(s, extendedPdu); /* extendedPdu (4 bytes) */
+ Stream_Read_UINT32(s, extraFlags1); /* extraFlags1 (4 bytes) */
+ Stream_Seek_UINT32(s); /* extraFlags2 (4 bytes), must be set to zero, reserved for future use */
+
+ if (VersionMajor != RDPDR_MAJOR_RDP_VERSION)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "unsupported RDPDR version %" PRIu16 ".%" PRIu16,
+ VersionMajor, VersionMinor);
+ return ERROR_INVALID_DATA;
+ }
+
+ switch (VersionMinor)
+ {
+ case RDPDR_MINOR_RDP_VERSION_13:
+ break;
+ case RDPDR_MINOR_RDP_VERSION_6_X:
+ break;
+ case RDPDR_MINOR_RDP_VERSION_5_2:
+ break;
+ case RDPDR_MINOR_RDP_VERSION_5_1:
+ break;
+ case RDPDR_MINOR_RDP_VERSION_5_0:
+ break;
+ default:
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "unsupported RDPDR minor version %" PRIu16 ".%" PRIu16, VersionMajor,
+ VersionMinor);
+ break;
+ }
+
+ if (header->Version == GENERAL_CAPABILITY_VERSION_02)
+ {
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, SpecialTypeDeviceCap); /* SpecialTypeDeviceCap (4 bytes) */
+ }
+
+ context->priv->UserLoggedOnPdu = (extendedPdu & RDPDR_USER_LOGGEDON_PDU) ? TRUE : FALSE;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_write_general_capability_set(RdpdrServerContext* context, wStream* s)
+{
+ UINT32 ioCode1 = 0;
+ UINT32 extendedPdu = 0;
+ UINT32 extraFlags1 = 0;
+ UINT32 SpecialTypeDeviceCap = 0;
+ const RDPDR_CAPABILITY_HEADER header = { CAP_GENERAL_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH + 36,
+ GENERAL_CAPABILITY_VERSION_02 };
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ ioCode1 = 0;
+ ioCode1 |= RDPDR_IRP_MJ_CREATE; /* always set */
+ ioCode1 |= RDPDR_IRP_MJ_CLEANUP; /* always set */
+ ioCode1 |= RDPDR_IRP_MJ_CLOSE; /* always set */
+ ioCode1 |= RDPDR_IRP_MJ_READ; /* always set */
+ ioCode1 |= RDPDR_IRP_MJ_WRITE; /* always set */
+ ioCode1 |= RDPDR_IRP_MJ_FLUSH_BUFFERS; /* always set */
+ ioCode1 |= RDPDR_IRP_MJ_SHUTDOWN; /* always set */
+ ioCode1 |= RDPDR_IRP_MJ_DEVICE_CONTROL; /* always set */
+ ioCode1 |= RDPDR_IRP_MJ_QUERY_VOLUME_INFORMATION; /* always set */
+ ioCode1 |= RDPDR_IRP_MJ_SET_VOLUME_INFORMATION; /* always set */
+ ioCode1 |= RDPDR_IRP_MJ_QUERY_INFORMATION; /* always set */
+ ioCode1 |= RDPDR_IRP_MJ_SET_INFORMATION; /* always set */
+ ioCode1 |= RDPDR_IRP_MJ_DIRECTORY_CONTROL; /* always set */
+ ioCode1 |= RDPDR_IRP_MJ_LOCK_CONTROL; /* always set */
+ ioCode1 |= RDPDR_IRP_MJ_QUERY_SECURITY; /* optional */
+ ioCode1 |= RDPDR_IRP_MJ_SET_SECURITY; /* optional */
+ extendedPdu = 0;
+ extendedPdu |= RDPDR_CLIENT_DISPLAY_NAME_PDU; /* always set */
+ extendedPdu |= RDPDR_DEVICE_REMOVE_PDUS; /* optional */
+
+ if (context->priv->UserLoggedOnPdu)
+ extendedPdu |= RDPDR_USER_LOGGEDON_PDU; /* optional */
+
+ extraFlags1 = 0;
+ extraFlags1 |= ENABLE_ASYNCIO; /* optional */
+ SpecialTypeDeviceCap = 0;
+
+ UINT error = rdpdr_write_capset_header(context->priv->log, s, &header);
+ if (error != CHANNEL_RC_OK)
+ return error;
+
+ const BYTE* data = Stream_ConstPointer(s);
+ const size_t start = Stream_GetPosition(s);
+ Stream_Write_UINT32(s, 0); /* osType (4 bytes), ignored on receipt */
+ Stream_Write_UINT32(s, 0); /* osVersion (4 bytes), unused and must be set to zero */
+ Stream_Write_UINT16(s, context->priv->VersionMajor); /* protocolMajorVersion (2 bytes) */
+ Stream_Write_UINT16(s, context->priv->VersionMinor); /* protocolMinorVersion (2 bytes) */
+ Stream_Write_UINT32(s, ioCode1); /* ioCode1 (4 bytes) */
+ Stream_Write_UINT32(s, 0); /* ioCode2 (4 bytes), must be set to zero, reserved for future use */
+ Stream_Write_UINT32(s, extendedPdu); /* extendedPdu (4 bytes) */
+ Stream_Write_UINT32(s, extraFlags1); /* extraFlags1 (4 bytes) */
+ Stream_Write_UINT32(
+ s, 0); /* extraFlags2 (4 bytes), must be set to zero, reserved for future use */
+ Stream_Write_UINT32(s, SpecialTypeDeviceCap); /* SpecialTypeDeviceCap (4 bytes) */
+ const size_t end = Stream_GetPosition(s);
+ return IFCALLRESULT(CHANNEL_RC_OK, context->SendCaps, context, &header, end - start, data);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_read_printer_capability_set(RdpdrServerContext* context, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+ WINPR_UNUSED(context);
+ WINPR_UNUSED(header);
+ WINPR_UNUSED(s);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_write_printer_capability_set(RdpdrServerContext* context, wStream* s)
+{
+ const RDPDR_CAPABILITY_HEADER header = { CAP_PRINTER_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH,
+ PRINT_CAPABILITY_VERSION_01 };
+ WINPR_UNUSED(context);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ return rdpdr_server_write_capability_set_header_cb(context, s, &header);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_read_port_capability_set(RdpdrServerContext* context, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_UNUSED(context);
+ WINPR_UNUSED(s);
+ WINPR_UNUSED(header);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_write_port_capability_set(RdpdrServerContext* context, wStream* s)
+{
+ const RDPDR_CAPABILITY_HEADER header = { CAP_PORT_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH,
+ PORT_CAPABILITY_VERSION_01 };
+ WINPR_UNUSED(context);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ return rdpdr_server_write_capability_set_header_cb(context, s, &header);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_read_drive_capability_set(RdpdrServerContext* context, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_UNUSED(context);
+ WINPR_UNUSED(header);
+ WINPR_UNUSED(s);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_write_drive_capability_set(RdpdrServerContext* context, wStream* s)
+{
+ const RDPDR_CAPABILITY_HEADER header = { CAP_DRIVE_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH,
+ DRIVE_CAPABILITY_VERSION_02 };
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_UNUSED(context);
+
+ return rdpdr_server_write_capability_set_header_cb(context, s, &header);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_read_smartcard_capability_set(RdpdrServerContext* context, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_UNUSED(context);
+ WINPR_UNUSED(header);
+ WINPR_UNUSED(s);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_write_smartcard_capability_set(RdpdrServerContext* context, wStream* s)
+{
+ const RDPDR_CAPABILITY_HEADER header = { CAP_SMARTCARD_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH,
+ SMARTCARD_CAPABILITY_VERSION_01 };
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ WINPR_UNUSED(context);
+
+ return rdpdr_server_write_capability_set_header_cb(context, s, &header);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_send_core_capability_request(RdpdrServerContext* context)
+{
+ wStream* s = NULL;
+ RDPDR_HEADER header = { 0 };
+ UINT16 numCapabilities = 0;
+ UINT error = 0;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ header.Component = RDPDR_CTYP_CORE;
+ header.PacketId = PAKID_CORE_SERVER_CAPABILITY;
+ numCapabilities = 1;
+
+ if ((context->supported & RDPDR_DTYP_FILESYSTEM) != 0)
+ numCapabilities++;
+
+ if (((context->supported & RDPDR_DTYP_PARALLEL) != 0) ||
+ ((context->supported & RDPDR_DTYP_SERIAL) != 0))
+ numCapabilities++;
+
+ if ((context->supported & RDPDR_DTYP_PRINT) != 0)
+ numCapabilities++;
+
+ if ((context->supported & RDPDR_DTYP_SMARTCARD) != 0)
+ numCapabilities++;
+
+ s = Stream_New(NULL, RDPDR_HEADER_LENGTH + 512);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, header.Component); /* Component (2 bytes) */
+ Stream_Write_UINT16(s, header.PacketId); /* PacketId (2 bytes) */
+ Stream_Write_UINT16(s, numCapabilities); /* numCapabilities (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* Padding (2 bytes) */
+
+ if ((error = rdpdr_server_write_general_capability_set(context, s)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpdr_server_write_general_capability_set failed with error %" PRIu32 "!",
+ error);
+ goto out;
+ }
+
+ if ((context->supported & RDPDR_DTYP_FILESYSTEM) != 0)
+ {
+ if ((error = rdpdr_server_write_drive_capability_set(context, s)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpdr_server_write_drive_capability_set failed with error %" PRIu32 "!",
+ error);
+ goto out;
+ }
+ }
+
+ if (((context->supported & RDPDR_DTYP_PARALLEL) != 0) ||
+ ((context->supported & RDPDR_DTYP_SERIAL) != 0))
+ {
+ if ((error = rdpdr_server_write_port_capability_set(context, s)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpdr_server_write_port_capability_set failed with error %" PRIu32 "!",
+ error);
+ goto out;
+ }
+ }
+
+ if ((context->supported & RDPDR_DTYP_PRINT) != 0)
+ {
+ if ((error = rdpdr_server_write_printer_capability_set(context, s)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpdr_server_write_printer_capability_set failed with error %" PRIu32 "!",
+ error);
+ goto out;
+ }
+ }
+
+ if ((context->supported & RDPDR_DTYP_SMARTCARD) != 0)
+ {
+ if ((error = rdpdr_server_write_smartcard_capability_set(context, s)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpdr_server_write_printer_capability_set failed with error %" PRIu32 "!",
+ error);
+ goto out;
+ }
+ }
+
+ return rdpdr_seal_send_free_request(context, s);
+out:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_receive_core_capability_response(RdpdrServerContext* context, wStream* s,
+ const RDPDR_HEADER* header)
+{
+ UINT status = 0;
+ UINT16 numCapabilities = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ WINPR_UNUSED(header);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, numCapabilities); /* numCapabilities (2 bytes) */
+ Stream_Seek_UINT16(s); /* Padding (2 bytes) */
+
+ UINT16 caps = 0;
+ for (UINT16 i = 0; i < numCapabilities; i++)
+ {
+ RDPDR_CAPABILITY_HEADER capabilityHeader = { 0 };
+ const size_t start = Stream_GetPosition(s);
+
+ if ((status = rdpdr_read_capset_header(context->priv->log, s, &capabilityHeader)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "failed with error %" PRIu32 "!", status);
+ return status;
+ }
+
+ status = IFCALLRESULT(CHANNEL_RC_OK, context->ReceiveCaps, context, &capabilityHeader,
+ Stream_GetRemainingLength(s), Stream_ConstPointer(s));
+ if (status != CHANNEL_RC_OK)
+ return status;
+
+ caps |= capabilityHeader.CapabilityType;
+ switch (capabilityHeader.CapabilityType)
+ {
+ case CAP_GENERAL_TYPE:
+ if ((status =
+ rdpdr_server_read_general_capability_set(context, s, &capabilityHeader)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "failed with error %" PRIu32 "!",
+ status);
+ return status;
+ }
+
+ break;
+
+ case CAP_PRINTER_TYPE:
+ if ((status =
+ rdpdr_server_read_printer_capability_set(context, s, &capabilityHeader)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "failed with error %" PRIu32 "!",
+ status);
+ return status;
+ }
+
+ break;
+
+ case CAP_PORT_TYPE:
+ if ((status = rdpdr_server_read_port_capability_set(context, s, &capabilityHeader)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "failed with error %" PRIu32 "!",
+ status);
+ return status;
+ }
+
+ break;
+
+ case CAP_DRIVE_TYPE:
+ if ((status =
+ rdpdr_server_read_drive_capability_set(context, s, &capabilityHeader)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "failed with error %" PRIu32 "!",
+ status);
+ return status;
+ }
+
+ break;
+
+ case CAP_SMARTCARD_TYPE:
+ if ((status =
+ rdpdr_server_read_smartcard_capability_set(context, s, &capabilityHeader)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "failed with error %" PRIu32 "!",
+ status);
+ return status;
+ }
+
+ break;
+
+ default:
+ WLog_Print(context->priv->log, WLOG_WARN, "Unknown capabilityType %" PRIu16 "",
+ capabilityHeader.CapabilityType);
+ Stream_Seek(s, capabilityHeader.CapabilityLength);
+ return ERROR_INVALID_DATA;
+ }
+
+ for (UINT16 x = 0; x < 16; x++)
+ {
+ const UINT16 mask = (UINT16)(1 << x);
+ if (((caps & mask) != 0) && ((context->supported & mask) == 0))
+ {
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "client sent capability %s we did not announce!",
+ freerdp_rdpdr_dtyp_string(mask));
+ }
+
+ /* we assume the server supports the capability. so only deactivate what the client did
+ * not respond with */
+ if ((caps & mask) == 0)
+ context->supported &= ~mask;
+ }
+
+ const size_t end = Stream_GetPosition(s);
+ const size_t diff = end - start;
+ if (diff != capabilityHeader.CapabilityLength + RDPDR_CAPABILITY_HEADER_LENGTH)
+ {
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "{capability %s[0x%04" PRIx16 "]} processed %" PRIuz
+ " bytes, but expected to be %" PRIu16,
+ rdpdr_cap_type_string(capabilityHeader.CapabilityType),
+ capabilityHeader.CapabilityType, diff, capabilityHeader.CapabilityLength);
+ }
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_send_client_id_confirm(RdpdrServerContext* context)
+{
+ wStream* s = NULL;
+ RDPDR_HEADER header = { 0 };
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ header.Component = RDPDR_CTYP_CORE;
+ header.PacketId = PAKID_CORE_CLIENTID_CONFIRM;
+ s = Stream_New(NULL, RDPDR_HEADER_LENGTH + 8);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, header.Component); /* Component (2 bytes) */
+ Stream_Write_UINT16(s, header.PacketId); /* PacketId (2 bytes) */
+ Stream_Write_UINT16(s, context->priv->VersionMajor); /* VersionMajor (2 bytes) */
+ Stream_Write_UINT16(s, context->priv->VersionMinor); /* VersionMinor (2 bytes) */
+ Stream_Write_UINT32(s, context->priv->ClientId); /* ClientId (4 bytes) */
+ return rdpdr_seal_send_free_request(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_receive_device_list_announce_request(RdpdrServerContext* context,
+ wStream* s,
+ const RDPDR_HEADER* header)
+{
+ UINT32 DeviceCount = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ WINPR_UNUSED(header);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, DeviceCount); /* DeviceCount (4 bytes) */
+ WLog_Print(context->priv->log, WLOG_DEBUG, "DeviceCount: %" PRIu32 "", DeviceCount);
+
+ for (UINT32 i = 0; i < DeviceCount; i++)
+ {
+ UINT error = 0;
+ RdpdrDevice device = { 0 };
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 20))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, device.DeviceType); /* DeviceType (4 bytes) */
+ Stream_Read_UINT32(s, device.DeviceId); /* DeviceId (4 bytes) */
+ Stream_Read(s, device.PreferredDosName, 8); /* PreferredDosName (8 bytes) */
+ Stream_Read_UINT32(s, device.DeviceDataLength); /* DeviceDataLength (4 bytes) */
+ device.DeviceData = Stream_Pointer(s);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, device.DeviceDataLength))
+ return ERROR_INVALID_DATA;
+
+ if (!rdpdr_add_device(context->priv, &device))
+ return ERROR_INTERNAL_ERROR;
+
+ error = IFCALLRESULT(CHANNEL_RC_OK, context->ReceiveDeviceAnnounce, context, &device);
+ if (error != CHANNEL_RC_OK)
+ return error;
+
+ switch (device.DeviceType)
+ {
+ case RDPDR_DTYP_FILESYSTEM:
+ if ((context->supported & RDPDR_DTYP_FILESYSTEM) != 0)
+ error = IFCALLRESULT(CHANNEL_RC_OK, context->OnDriveCreate, context, &device);
+ break;
+
+ case RDPDR_DTYP_PRINT:
+ if ((context->supported & RDPDR_DTYP_PRINT) != 0)
+ error = IFCALLRESULT(CHANNEL_RC_OK, context->OnPrinterCreate, context, &device);
+ break;
+
+ case RDPDR_DTYP_SERIAL:
+ if (device.DeviceDataLength != 0)
+ {
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[rdpdr] RDPDR_DTYP_SERIAL::DeviceDataLength != 0 [%" PRIu32 "]",
+ device.DeviceDataLength);
+ error = ERROR_INVALID_DATA;
+ }
+ else if ((context->supported & RDPDR_DTYP_SERIAL) != 0)
+ error =
+ IFCALLRESULT(CHANNEL_RC_OK, context->OnSerialPortCreate, context, &device);
+ break;
+
+ case RDPDR_DTYP_PARALLEL:
+ if (device.DeviceDataLength != 0)
+ {
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[rdpdr] RDPDR_DTYP_PARALLEL::DeviceDataLength != 0 [%" PRIu32 "]",
+ device.DeviceDataLength);
+ error = ERROR_INVALID_DATA;
+ }
+ else if ((context->supported & RDPDR_DTYP_PARALLEL) != 0)
+ error = IFCALLRESULT(CHANNEL_RC_OK, context->OnParallelPortCreate, context,
+ &device);
+ break;
+
+ case RDPDR_DTYP_SMARTCARD:
+ if (device.DeviceDataLength != 0)
+ {
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[rdpdr] RDPDR_DTYP_SMARTCARD::DeviceDataLength != 0 [%" PRIu32 "]",
+ device.DeviceDataLength);
+ error = ERROR_INVALID_DATA;
+ }
+ else if ((context->supported & RDPDR_DTYP_SMARTCARD) != 0)
+ error =
+ IFCALLRESULT(CHANNEL_RC_OK, context->OnSmartcardCreate, context, &device);
+ break;
+
+ default:
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[MS-RDPEFS] 2.2.2.9 Client Device List Announce Request "
+ "(DR_CORE_DEVICELIST_ANNOUNCE_REQ) unknown device type %04" PRIx16
+ " at position %" PRIu32,
+ device.DeviceType, i);
+ error = ERROR_INVALID_DATA;
+ break;
+ }
+
+ if (error != CHANNEL_RC_OK)
+ return error;
+
+ Stream_Seek(s, device.DeviceDataLength);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_receive_device_list_remove_request(RdpdrServerContext* context, wStream* s,
+ const RDPDR_HEADER* header)
+{
+ UINT32 DeviceCount = 0;
+ UINT32 DeviceType = 0;
+ UINT32 DeviceId = 0;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ WINPR_UNUSED(header);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, DeviceCount); /* DeviceCount (4 bytes) */
+ WLog_Print(context->priv->log, WLOG_DEBUG, "DeviceCount: %" PRIu32 "", DeviceCount);
+
+ for (UINT32 i = 0; i < DeviceCount; i++)
+ {
+ UINT error = 0;
+ const RdpdrDevice* device = NULL;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, DeviceId); /* DeviceId (4 bytes) */
+ device = rdpdr_get_device_by_id(context->priv, DeviceId);
+ WLog_Print(context->priv->log, WLOG_DEBUG, "Device %" PRIu32 " Id: 0x%08" PRIX32 "", i,
+ DeviceId);
+ DeviceType = 0;
+ if (device)
+ DeviceType = device->DeviceType;
+
+ error =
+ IFCALLRESULT(CHANNEL_RC_OK, context->ReceiveDeviceRemove, context, DeviceId, device);
+ if (error != CHANNEL_RC_OK)
+ return error;
+
+ switch (DeviceType)
+ {
+ case RDPDR_DTYP_FILESYSTEM:
+ if ((context->supported & RDPDR_DTYP_FILESYSTEM) != 0)
+ error = IFCALLRESULT(CHANNEL_RC_OK, context->OnDriveDelete, context, DeviceId);
+ break;
+
+ case RDPDR_DTYP_PRINT:
+ if ((context->supported & RDPDR_DTYP_PRINT) != 0)
+ error =
+ IFCALLRESULT(CHANNEL_RC_OK, context->OnPrinterDelete, context, DeviceId);
+ break;
+
+ case RDPDR_DTYP_SERIAL:
+ if ((context->supported & RDPDR_DTYP_SERIAL) != 0)
+ error =
+ IFCALLRESULT(CHANNEL_RC_OK, context->OnSerialPortDelete, context, DeviceId);
+ break;
+
+ case RDPDR_DTYP_PARALLEL:
+ if ((context->supported & RDPDR_DTYP_PARALLEL) != 0)
+ error = IFCALLRESULT(CHANNEL_RC_OK, context->OnParallelPortDelete, context,
+ DeviceId);
+ break;
+
+ case RDPDR_DTYP_SMARTCARD:
+ if ((context->supported & RDPDR_DTYP_SMARTCARD) != 0)
+ error =
+ IFCALLRESULT(CHANNEL_RC_OK, context->OnSmartcardDelete, context, DeviceId);
+ break;
+
+ default:
+ break;
+ }
+
+ if (error != CHANNEL_RC_OK)
+ return error;
+
+ if (!rdpdr_remove_device_by_id(context->priv, DeviceId))
+ return ERROR_INVALID_DATA;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_receive_io_create_request(RdpdrServerContext* context, wStream* s,
+ UINT32 DeviceId, UINT32 FileId,
+ UINT32 CompletionId)
+{
+ const WCHAR* path = NULL;
+ UINT32 DesiredAccess = 0;
+ UINT32 AllocationSize = 0;
+ UINT32 FileAttributes = 0;
+ UINT32 SharedAccess = 0;
+ UINT32 CreateDisposition = 0;
+ UINT32 CreateOptions = 0;
+ UINT32 PathLength = 0;
+
+ WINPR_ASSERT(context);
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, DesiredAccess);
+ Stream_Read_UINT32(s, AllocationSize);
+ Stream_Read_UINT32(s, FileAttributes);
+ Stream_Read_UINT32(s, SharedAccess);
+ Stream_Read_UINT32(s, CreateDisposition);
+ Stream_Read_UINT32(s, CreateOptions);
+ Stream_Read_UINT32(s, PathLength);
+
+ path = rdpdr_read_ustring(context->priv->log, s, PathLength);
+ if (!path && (PathLength > 0))
+ return ERROR_INVALID_DATA;
+
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[MS-RDPEFS] 2.2.1.4.1 Device Create Request (DR_CREATE_REQ) not implemented");
+ WLog_Print(context->priv->log, WLOG_WARN, "TODO");
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_receive_io_close_request(RdpdrServerContext* context, wStream* s,
+ UINT32 DeviceId, UINT32 FileId,
+ UINT32 CompletionId)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Seek(s, 32); /* Padding */
+
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[MS-RDPEFS] 2.2.1.4.2 Device Close Request (DR_CLOSE_REQ) not implemented");
+ WLog_Print(context->priv->log, WLOG_WARN, "TODO");
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_receive_io_read_request(RdpdrServerContext* context, wStream* s,
+ UINT32 DeviceId, UINT32 FileId,
+ UINT32 CompletionId)
+{
+ UINT32 Length = 0;
+ UINT64 Offset = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, Length);
+ Stream_Read_UINT64(s, Offset);
+ Stream_Seek(s, 20); /* Padding */
+
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[MS-RDPEFS] 2.2.1.4.3 Device Read Request (DR_READ_REQ) not implemented");
+ WLog_Print(context->priv->log, WLOG_WARN, "TODO");
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_receive_io_write_request(RdpdrServerContext* context, wStream* s,
+ UINT32 DeviceId, UINT32 FileId,
+ UINT32 CompletionId)
+{
+ UINT32 Length = 0;
+ UINT64 Offset = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, Length);
+ Stream_Read_UINT64(s, Offset);
+ Stream_Seek(s, 20); /* Padding */
+
+ const BYTE* data = Stream_ConstPointer(s);
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, Length))
+ return ERROR_INVALID_DATA;
+ Stream_Seek(s, Length);
+
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[MS-RDPEFS] 2.2.1.4.4 Device Write Request (DR_WRITE_REQ) not implemented");
+ WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", data);
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_receive_io_device_control_request(RdpdrServerContext* context, wStream* s,
+ UINT32 DeviceId, UINT32 FileId,
+ UINT32 CompletionId)
+{
+ UINT32 OutputBufferLength = 0;
+ UINT32 InputBufferLength = 0;
+ UINT32 IoControlCode = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, OutputBufferLength);
+ Stream_Read_UINT32(s, InputBufferLength);
+ Stream_Read_UINT32(s, IoControlCode);
+ Stream_Seek(s, 20); /* Padding */
+
+ const BYTE* InputBuffer = Stream_ConstPointer(s);
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, InputBufferLength))
+ return ERROR_INVALID_DATA;
+ Stream_Seek(s, InputBufferLength);
+
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[MS-RDPEFS] 2.2.1.4.5 Device Control Request (DR_CONTROL_REQ) not implemented");
+ WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", InputBuffer);
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_receive_io_query_volume_information_request(RdpdrServerContext* context,
+ wStream* s, UINT32 DeviceId,
+ UINT32 FileId,
+ UINT32 CompletionId)
+{
+ UINT32 FsInformationClass = 0;
+ UINT32 Length = 0;
+
+ WINPR_ASSERT(context);
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, FsInformationClass);
+ Stream_Read_UINT32(s, Length);
+ Stream_Seek(s, 24); /* Padding */
+
+ const BYTE* QueryVolumeBuffer = Stream_ConstPointer(s);
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, Length))
+ return ERROR_INVALID_DATA;
+ Stream_Seek(s, Length);
+
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[MS-RDPEFS] 2.2.3.3.6 Server Drive Query Volume Information Request "
+ "(DR_DRIVE_QUERY_VOLUME_INFORMATION_REQ) not implemented");
+ WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", QueryVolumeBuffer);
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_receive_io_set_volume_information_request(RdpdrServerContext* context,
+ wStream* s, UINT32 DeviceId,
+ UINT32 FileId,
+ UINT32 CompletionId)
+{
+ UINT32 FsInformationClass = 0;
+ UINT32 Length = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, FsInformationClass);
+ Stream_Read_UINT32(s, Length);
+ Stream_Seek(s, 24); /* Padding */
+
+ const BYTE* SetVolumeBuffer = Stream_ConstPointer(s);
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, Length))
+ return ERROR_INVALID_DATA;
+ Stream_Seek(s, Length);
+
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[MS-RDPEFS] 2.2.3.3.7 Server Drive Set Volume Information Request "
+ "(DR_DRIVE_SET_VOLUME_INFORMATION_REQ) not implemented");
+ WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", SetVolumeBuffer);
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_receive_io_query_information_request(RdpdrServerContext* context,
+ wStream* s, UINT32 DeviceId,
+ UINT32 FileId, UINT32 CompletionId)
+{
+ UINT32 FsInformationClass = 0;
+ UINT32 Length = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, FsInformationClass);
+ Stream_Read_UINT32(s, Length);
+ Stream_Seek(s, 24); /* Padding */
+
+ const BYTE* QueryBuffer = Stream_ConstPointer(s);
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, Length))
+ return ERROR_INVALID_DATA;
+ Stream_Seek(s, Length);
+
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[MS-RDPEFS] 2.2.3.3.8 Server Drive Query Information Request "
+ "(DR_DRIVE_QUERY_INFORMATION_REQ) not implemented");
+ WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", QueryBuffer);
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_receive_io_set_information_request(RdpdrServerContext* context, wStream* s,
+ UINT32 DeviceId, UINT32 FileId,
+ UINT32 CompletionId)
+{
+ UINT32 FsInformationClass = 0;
+ UINT32 Length = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, FsInformationClass);
+ Stream_Read_UINT32(s, Length);
+ Stream_Seek(s, 24); /* Padding */
+
+ const BYTE* SetBuffer = Stream_ConstPointer(s);
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, Length))
+ return ERROR_INVALID_DATA;
+ Stream_Seek(s, Length);
+
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[MS-RDPEFS] 2.2.3.3.9 Server Drive Set Information Request "
+ "(DR_DRIVE_SET_INFORMATION_REQ) not implemented");
+ WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", SetBuffer);
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_receive_io_query_directory_request(RdpdrServerContext* context, wStream* s,
+ UINT32 DeviceId, UINT32 FileId,
+ UINT32 CompletionId)
+{
+ BYTE InitialQuery = 0;
+ UINT32 FsInformationClass = 0;
+ UINT32 PathLength = 0;
+ const WCHAR* Path = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, FsInformationClass);
+ Stream_Read_UINT8(s, InitialQuery);
+ Stream_Read_UINT32(s, PathLength);
+ Stream_Seek(s, 23); /* Padding */
+
+ Path = rdpdr_read_ustring(context->priv->log, s, PathLength);
+ if (!Path && (PathLength > 0))
+ return ERROR_INVALID_DATA;
+
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[MS-RDPEFS] 2.2.3.3.10 Server Drive Query Directory Request "
+ "(DR_DRIVE_QUERY_DIRECTORY_REQ) not implemented");
+ WLog_Print(context->priv->log, WLOG_WARN, "TODO");
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_receive_io_change_directory_request(RdpdrServerContext* context,
+ wStream* s, UINT32 DeviceId,
+ UINT32 FileId, UINT32 CompletionId)
+{
+ BYTE WatchTree = 0;
+ UINT32 CompletionFilter = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT8(s, WatchTree);
+ Stream_Read_UINT32(s, CompletionFilter);
+ Stream_Seek(s, 27); /* Padding */
+
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[MS-RDPEFS] 2.2.3.3.11 Server Drive NotifyChange Directory Request "
+ "(DR_DRIVE_NOTIFY_CHANGE_DIRECTORY_REQ) not implemented");
+ WLog_Print(context->priv->log, WLOG_WARN, "TODO");
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_receive_io_directory_control_request(RdpdrServerContext* context,
+ wStream* s, UINT32 DeviceId,
+ UINT32 FileId, UINT32 CompletionId,
+ UINT32 MinorFunction)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ switch (MinorFunction)
+ {
+ case IRP_MN_QUERY_DIRECTORY:
+ return rdpdr_server_receive_io_query_directory_request(context, s, DeviceId, FileId,
+ CompletionId);
+ case IRP_MN_NOTIFY_CHANGE_DIRECTORY:
+ return rdpdr_server_receive_io_change_directory_request(context, s, DeviceId, FileId,
+ CompletionId);
+ default:
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[MS-RDPEFS] 2.2.1.4 Device I/O Request (DR_DEVICE_IOREQUEST) "
+ "MajorFunction=%s, MinorFunction=%08" PRIx32 " is not supported",
+ rdpdr_irp_string(IRP_MJ_DIRECTORY_CONTROL), MinorFunction);
+ return ERROR_INVALID_DATA;
+ }
+}
+
+static UINT rdpdr_server_receive_io_lock_control_request(RdpdrServerContext* context, wStream* s,
+ UINT32 DeviceId, UINT32 FileId,
+ UINT32 CompletionId)
+{
+ UINT32 Operation = 0;
+ UINT32 Lock = 0;
+ UINT32 NumLocks = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, Operation);
+ Stream_Read_UINT32(s, Lock);
+ Stream_Read_UINT32(s, NumLocks);
+ Stream_Seek(s, 20); /* Padding */
+
+ Lock &= 0x01; /* Only byte 0 is of importance */
+
+ for (UINT32 x = 0; x < NumLocks; x++)
+ {
+ UINT64 Length = 0;
+ UINT64 Offset = 0;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 16))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT64(s, Length);
+ Stream_Read_UINT64(s, Offset);
+ }
+
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[MS-RDPEFS] 2.2.3.3.12 Server Drive Lock Control Request (DR_DRIVE_LOCK_REQ) "
+ "not implemented");
+ WLog_Print(context->priv->log, WLOG_WARN, "TODO");
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_receive_device_io_request(RdpdrServerContext* context, wStream* s,
+ const RDPDR_HEADER* header)
+{
+ UINT32 DeviceId = 0;
+ UINT32 FileId = 0;
+ UINT32 CompletionId = 0;
+ UINT32 MajorFunction = 0;
+ UINT32 MinorFunction = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(header);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 20))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, DeviceId);
+ Stream_Read_UINT32(s, FileId);
+ Stream_Read_UINT32(s, CompletionId);
+ Stream_Read_UINT32(s, MajorFunction);
+ Stream_Read_UINT32(s, MinorFunction);
+ if ((MinorFunction != 0) && (MajorFunction != IRP_MJ_DIRECTORY_CONTROL))
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[MS-RDPEFS] 2.2.1.4 Device I/O Request (DR_DEVICE_IOREQUEST) MajorFunction=%s, "
+ "MinorFunction=0x%08" PRIx32 " != 0",
+ rdpdr_irp_string(MajorFunction), MinorFunction);
+
+ switch (MajorFunction)
+ {
+ case IRP_MJ_CREATE:
+ return rdpdr_server_receive_io_create_request(context, s, DeviceId, FileId,
+ CompletionId);
+ case IRP_MJ_CLOSE:
+ return rdpdr_server_receive_io_close_request(context, s, DeviceId, FileId,
+ CompletionId);
+ case IRP_MJ_READ:
+ return rdpdr_server_receive_io_read_request(context, s, DeviceId, FileId, CompletionId);
+ case IRP_MJ_WRITE:
+ return rdpdr_server_receive_io_write_request(context, s, DeviceId, FileId,
+ CompletionId);
+ case IRP_MJ_DEVICE_CONTROL:
+ return rdpdr_server_receive_io_device_control_request(context, s, DeviceId, FileId,
+ CompletionId);
+ case IRP_MJ_QUERY_VOLUME_INFORMATION:
+ return rdpdr_server_receive_io_query_volume_information_request(context, s, DeviceId,
+ FileId, CompletionId);
+ case IRP_MJ_QUERY_INFORMATION:
+ return rdpdr_server_receive_io_query_information_request(context, s, DeviceId, FileId,
+ CompletionId);
+ case IRP_MJ_SET_INFORMATION:
+ return rdpdr_server_receive_io_set_information_request(context, s, DeviceId, FileId,
+ CompletionId);
+ case IRP_MJ_DIRECTORY_CONTROL:
+ return rdpdr_server_receive_io_directory_control_request(context, s, DeviceId, FileId,
+ CompletionId, MinorFunction);
+ case IRP_MJ_LOCK_CONTROL:
+ return rdpdr_server_receive_io_lock_control_request(context, s, DeviceId, FileId,
+ CompletionId);
+ default:
+ WLog_Print(
+ context->priv->log, WLOG_WARN,
+ "[MS-RDPEFS] 2.2.1.4 Device I/O Request (DR_DEVICE_IOREQUEST) not implemented");
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "got DeviceId=0x%08" PRIx32 ", FileId=0x%08" PRIx32
+ ", CompletionId=0x%08" PRIx32 ", MajorFunction=0x%08" PRIx32
+ ", MinorFunction=0x%08" PRIx32,
+ DeviceId, FileId, CompletionId, MajorFunction, MinorFunction);
+ return ERROR_INVALID_DATA;
+ }
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_receive_device_io_completion(RdpdrServerContext* context, wStream* s,
+ const RDPDR_HEADER* header)
+{
+ UINT32 deviceId = 0;
+ UINT32 completionId = 0;
+ UINT32 ioStatus = 0;
+ RDPDR_IRP* irp = NULL;
+ UINT error = CHANNEL_RC_OK;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ WINPR_UNUSED(header);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, deviceId);
+ Stream_Read_UINT32(s, completionId);
+ Stream_Read_UINT32(s, ioStatus);
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "deviceId=%" PRIu32 ", completionId=0x%" PRIx32 ", ioStatus=0x%" PRIx32 "", deviceId,
+ completionId, ioStatus);
+ irp = rdpdr_server_dequeue_irp(context, completionId);
+
+ if (!irp)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "IRP not found for completionId=0x%" PRIx32 "",
+ completionId);
+ return CHANNEL_RC_OK;
+ }
+
+ /* Invoke the callback. */
+ if (irp->Callback)
+ {
+ error = (*irp->Callback)(context, s, irp, deviceId, completionId, ioStatus);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_send_user_logged_on(RdpdrServerContext* context)
+{
+ wStream* s = NULL;
+ RDPDR_HEADER header = { 0 };
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ header.Component = RDPDR_CTYP_CORE;
+ header.PacketId = PAKID_CORE_USER_LOGGEDON;
+ s = Stream_New(NULL, RDPDR_HEADER_LENGTH);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, header.Component); /* Component (2 bytes) */
+ Stream_Write_UINT16(s, header.PacketId); /* PacketId (2 bytes) */
+ return rdpdr_seal_send_free_request(context, s);
+}
+
+static UINT rdpdr_server_receive_prn_cache_add_printer(RdpdrServerContext* context, wStream* s)
+{
+ char PortDosName[9] = { 0 };
+ UINT32 PnPNameLen = 0;
+ UINT32 DriverNameLen = 0;
+ UINT32 PrinterNameLen = 0;
+ UINT32 CachedFieldsLen = 0;
+ const WCHAR* PnPName = NULL;
+ const WCHAR* DriverName = NULL;
+ const WCHAR* PrinterName = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 24))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read(s, PortDosName, 8);
+ Stream_Read_UINT32(s, PnPNameLen);
+ Stream_Read_UINT32(s, DriverNameLen);
+ Stream_Read_UINT32(s, PrinterNameLen);
+ Stream_Read_UINT32(s, CachedFieldsLen);
+
+ PnPName = rdpdr_read_ustring(context->priv->log, s, PnPNameLen);
+ if (!PnPName && (PnPNameLen > 0))
+ return ERROR_INVALID_DATA;
+ DriverName = rdpdr_read_ustring(context->priv->log, s, DriverNameLen);
+ if (!DriverName && (DriverNameLen > 0))
+ return ERROR_INVALID_DATA;
+ PrinterName = rdpdr_read_ustring(context->priv->log, s, PrinterNameLen);
+ if (!PrinterName && (PrinterNameLen > 0))
+ return ERROR_INVALID_DATA;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, CachedFieldsLen))
+ return ERROR_INVALID_DATA;
+ Stream_Seek(s, CachedFieldsLen);
+
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[MS-RDPEPC] 2.2.2.3 Add Printer Cachedata (DR_PRN_ADD_CACHEDATA) not implemented");
+ WLog_Print(context->priv->log, WLOG_WARN, "TODO");
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_receive_prn_cache_update_printer(RdpdrServerContext* context, wStream* s)
+{
+ UINT32 PrinterNameLen = 0;
+ UINT32 CachedFieldsLen = 0;
+ const WCHAR* PrinterName = NULL;
+
+ WINPR_ASSERT(context);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, PrinterNameLen);
+ Stream_Read_UINT32(s, CachedFieldsLen);
+
+ PrinterName = rdpdr_read_ustring(context->priv->log, s, PrinterNameLen);
+ if (!PrinterName && (PrinterNameLen > 0))
+ return ERROR_INVALID_DATA;
+
+ const BYTE* config = Stream_ConstPointer(s);
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, CachedFieldsLen))
+ return ERROR_INVALID_DATA;
+ Stream_Seek(s, CachedFieldsLen);
+
+ WLog_Print(
+ context->priv->log, WLOG_WARN,
+ "[MS-RDPEPC] 2.2.2.4 Update Printer Cachedata (DR_PRN_UPDATE_CACHEDATA) not implemented");
+ WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", config);
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_receive_prn_cache_delete_printer(RdpdrServerContext* context, wStream* s)
+{
+ UINT32 PrinterNameLen = 0;
+ const WCHAR* PrinterName = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, PrinterNameLen);
+
+ PrinterName = rdpdr_read_ustring(context->priv->log, s, PrinterNameLen);
+ if (!PrinterName && (PrinterNameLen > 0))
+ return ERROR_INVALID_DATA;
+
+ WLog_Print(
+ context->priv->log, WLOG_WARN,
+ "[MS-RDPEPC] 2.2.2.5 Delete Printer Cachedata (DR_PRN_DELETE_CACHEDATA) not implemented");
+ WLog_Print(context->priv->log, WLOG_WARN, "TODO");
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_receive_prn_cache_rename_cachedata(RdpdrServerContext* context, wStream* s)
+{
+ UINT32 OldPrinterNameLen = 0;
+ UINT32 NewPrinterNameLen = 0;
+ const WCHAR* OldPrinterName = NULL;
+ const WCHAR* NewPrinterName = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, OldPrinterNameLen);
+ Stream_Read_UINT32(s, NewPrinterNameLen);
+
+ OldPrinterName = rdpdr_read_ustring(context->priv->log, s, OldPrinterNameLen);
+ if (!OldPrinterName && (OldPrinterNameLen > 0))
+ return ERROR_INVALID_DATA;
+ NewPrinterName = rdpdr_read_ustring(context->priv->log, s, NewPrinterNameLen);
+ if (!NewPrinterName && (NewPrinterNameLen > 0))
+ return ERROR_INVALID_DATA;
+
+ WLog_Print(
+ context->priv->log, WLOG_WARN,
+ "[MS-RDPEPC] 2.2.2.6 Rename Printer Cachedata (DR_PRN_RENAME_CACHEDATA) not implemented");
+ WLog_Print(context->priv->log, WLOG_WARN, "TODO");
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_receive_prn_cache_data_request(RdpdrServerContext* context, wStream* s,
+ const RDPDR_HEADER* header)
+{
+ UINT32 EventId = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(header);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, EventId);
+ switch (EventId)
+ {
+ case RDPDR_ADD_PRINTER_EVENT:
+ return rdpdr_server_receive_prn_cache_add_printer(context, s);
+ case RDPDR_UPDATE_PRINTER_EVENT:
+ return rdpdr_server_receive_prn_cache_update_printer(context, s);
+ case RDPDR_DELETE_PRINTER_EVENT:
+ return rdpdr_server_receive_prn_cache_delete_printer(context, s);
+ case RDPDR_RENAME_PRINTER_EVENT:
+ return rdpdr_server_receive_prn_cache_rename_cachedata(context, s);
+ default:
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "[MS-RDPEPC] PAKID_PRN_CACHE_DATA unknown EventId=0x%08" PRIx32, EventId);
+ return ERROR_INVALID_DATA;
+ }
+}
+
+static UINT rdpdr_server_receive_prn_using_xps_request(RdpdrServerContext* context, wStream* s,
+ const RDPDR_HEADER* header)
+{
+ UINT32 PrinterId = 0;
+ UINT32 Flags = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(header);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, PrinterId);
+ Stream_Read_UINT32(s, Flags);
+
+ WLog_Print(
+ context->priv->log, WLOG_WARN,
+ "[MS-RDPEPC] 2.2.2.2 Server Printer Set XPS Mode (DR_PRN_USING_XPS) not implemented");
+ WLog_Print(context->priv->log, WLOG_WARN, "PrinterId=0x%08" PRIx32 ", Flags=0x%08" PRIx32,
+ PrinterId, Flags);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_receive_pdu(RdpdrServerContext* context, wStream* s,
+ const RDPDR_HEADER* header)
+{
+ UINT error = ERROR_INVALID_DATA;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "receiving message {Component %s[%04" PRIx16 "], PacketId %s[%04" PRIx16 "]",
+ rdpdr_component_string(header->Component), header->Component,
+ rdpdr_packetid_string(header->PacketId), header->PacketId);
+
+ if (header->Component == RDPDR_CTYP_CORE)
+ {
+ switch (header->PacketId)
+ {
+ case PAKID_CORE_SERVER_ANNOUNCE:
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "[MS-RDPEFS] 2.2.2.2 Server Announce Request "
+ "(DR_CORE_SERVER_ANNOUNCE_REQ) must not be sent by a client!");
+ break;
+
+ case PAKID_CORE_CLIENTID_CONFIRM:
+ error = rdpdr_server_receive_announce_response(context, s, header);
+ break;
+
+ case PAKID_CORE_CLIENT_NAME:
+ error = rdpdr_server_receive_client_name_request(context, s, header);
+ if (error == CHANNEL_RC_OK)
+ error = rdpdr_server_send_core_capability_request(context);
+ if (error == CHANNEL_RC_OK)
+ error = rdpdr_server_send_client_id_confirm(context);
+ break;
+
+ case PAKID_CORE_USER_LOGGEDON:
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "[MS-RDPEFS] 2.2.2.5 Server User Logged On (DR_CORE_USER_LOGGEDON) "
+ "must not be sent by a client!");
+ break;
+
+ case PAKID_CORE_SERVER_CAPABILITY:
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "[MS-RDPEFS] 2.2.2.7 Server Core Capability Request "
+ "(DR_CORE_CAPABILITY_REQ) must not be sent by a client!");
+ break;
+
+ case PAKID_CORE_CLIENT_CAPABILITY:
+ error = rdpdr_server_receive_core_capability_response(context, s, header);
+ if (error == CHANNEL_RC_OK)
+ {
+ if (context->priv->UserLoggedOnPdu)
+ error = rdpdr_server_send_user_logged_on(context);
+ }
+
+ break;
+
+ case PAKID_CORE_DEVICELIST_ANNOUNCE:
+ error = rdpdr_server_receive_device_list_announce_request(context, s, header);
+ break;
+
+ case PAKID_CORE_DEVICELIST_REMOVE:
+ error = rdpdr_server_receive_device_list_remove_request(context, s, header);
+ break;
+
+ case PAKID_CORE_DEVICE_REPLY:
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "[MS-RDPEFS] 2.2.2.1 Server Device Announce Response "
+ "(DR_CORE_DEVICE_ANNOUNCE_RSP) must not be sent by a client!");
+ break;
+
+ case PAKID_CORE_DEVICE_IOREQUEST:
+ error = rdpdr_server_receive_device_io_request(context, s, header);
+ break;
+
+ case PAKID_CORE_DEVICE_IOCOMPLETION:
+ error = rdpdr_server_receive_device_io_completion(context, s, header);
+ break;
+
+ default:
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "Unknown RDPDR_HEADER.Component: %s [0x%04" PRIx16 "], PacketId: %s",
+ rdpdr_component_string(header->Component), header->Component,
+ rdpdr_packetid_string(header->PacketId));
+ break;
+ }
+ }
+ else if (header->Component == RDPDR_CTYP_PRN)
+ {
+ switch (header->PacketId)
+ {
+ case PAKID_PRN_CACHE_DATA:
+ error = rdpdr_server_receive_prn_cache_data_request(context, s, header);
+ break;
+
+ case PAKID_PRN_USING_XPS:
+ error = rdpdr_server_receive_prn_using_xps_request(context, s, header);
+ break;
+
+ default:
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "Unknown RDPDR_HEADER.Component: %s [0x%04" PRIx16 "], PacketId: %s",
+ rdpdr_component_string(header->Component), header->Component,
+ rdpdr_packetid_string(header->PacketId));
+ break;
+ }
+ }
+ else
+ {
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "Unknown RDPDR_HEADER.Component: %s [0x%04" PRIx16 "], PacketId: %s",
+ rdpdr_component_string(header->Component), header->Component,
+ rdpdr_packetid_string(header->PacketId));
+ }
+
+ return IFCALLRESULT(error, context->ReceivePDU, context, header, error);
+}
+
+static DWORD WINAPI rdpdr_server_thread(LPVOID arg)
+{
+ DWORD status = 0;
+ DWORD nCount = 0;
+ void* buffer = NULL;
+ HANDLE events[8] = { 0 };
+ HANDLE ChannelEvent = NULL;
+ DWORD BytesReturned = 0;
+ UINT error = 0;
+ RdpdrServerContext* context = (RdpdrServerContext*)arg;
+ wStream* s = Stream_New(NULL, 4096);
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ if (WTSVirtualChannelQuery(context->priv->ChannelHandle, WTSVirtualEventHandle, &buffer,
+ &BytesReturned) == TRUE)
+ {
+ if (BytesReturned == sizeof(HANDLE))
+ CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE));
+
+ WTSFreeMemory(buffer);
+ }
+
+ nCount = 0;
+ events[nCount++] = ChannelEvent;
+ events[nCount++] = context->priv->StopEvent;
+
+ if ((error = rdpdr_server_send_announce_request(context)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpdr_server_send_announce_request failed with error %" PRIu32 "!", error);
+ goto out_stream;
+ }
+
+ while (1)
+ {
+ size_t capacity = 0;
+ BytesReturned = 0;
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "WaitForMultipleObjects failed with error %" PRIu32 "!", error);
+ goto out_stream;
+ }
+
+ status = WaitForSingleObject(context->priv->StopEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "WaitForSingleObject failed with error %" PRIu32 "!", error);
+ goto out_stream;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ if (!WTSVirtualChannelRead(context->priv->ChannelHandle, 0, NULL, 0, &BytesReturned))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelRead failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+ if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ capacity = MIN(Stream_Capacity(s), ULONG_MAX);
+ if (!WTSVirtualChannelRead(context->priv->ChannelHandle, 0, (PCHAR)Stream_Buffer(s),
+ (ULONG)capacity, &BytesReturned))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelRead failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (BytesReturned >= RDPDR_HEADER_LENGTH)
+ {
+ Stream_SetPosition(s, 0);
+ Stream_SetLength(s, BytesReturned);
+
+ while (Stream_GetRemainingLength(s) >= RDPDR_HEADER_LENGTH)
+ {
+ RDPDR_HEADER header = { 0 };
+
+ Stream_Read_UINT16(s, header.Component); /* Component (2 bytes) */
+ Stream_Read_UINT16(s, header.PacketId); /* PacketId (2 bytes) */
+
+ if ((error = rdpdr_server_receive_pdu(context, s, &header)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpdr_server_receive_pdu failed with error %" PRIu32 "!", error);
+ goto out_stream;
+ }
+ }
+ }
+ }
+
+out_stream:
+ Stream_Free(s, TRUE);
+out:
+
+ if (error && context->rdpcontext)
+ setChannelError(context->rdpcontext, error, "rdpdr_server_thread reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_start(RdpdrServerContext* context)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ context->priv->ChannelHandle =
+ WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, RDPDR_SVC_CHANNEL_NAME);
+
+ if (!context->priv->ChannelHandle)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelOpen failed!");
+ return CHANNEL_RC_BAD_CHANNEL;
+ }
+
+ if (!(context->priv->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!(context->priv->Thread =
+ CreateThread(NULL, 0, rdpdr_server_thread, (void*)context, 0, NULL)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "CreateThread failed!");
+ CloseHandle(context->priv->StopEvent);
+ context->priv->StopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_stop(RdpdrServerContext* context)
+{
+ UINT error = 0;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if (context->priv->StopEvent)
+ {
+ SetEvent(context->priv->StopEvent);
+
+ if (WaitForSingleObject(context->priv->Thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "WaitForSingleObject failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ CloseHandle(context->priv->Thread);
+ context->priv->Thread = NULL;
+ CloseHandle(context->priv->StopEvent);
+ context->priv->StopEvent = NULL;
+ }
+
+ if (context->priv->ChannelHandle)
+ {
+ WTSVirtualChannelClose(context->priv->ChannelHandle);
+ context->priv->ChannelHandle = NULL;
+ }
+ return CHANNEL_RC_OK;
+}
+
+static void rdpdr_server_write_device_iorequest(wStream* s, UINT32 deviceId, UINT32 fileId,
+ UINT32 completionId, UINT32 majorFunction,
+ UINT32 minorFunction)
+{
+ Stream_Write_UINT16(s, RDPDR_CTYP_CORE); /* Component (2 bytes) */
+ Stream_Write_UINT16(s, PAKID_CORE_DEVICE_IOREQUEST); /* PacketId (2 bytes) */
+ Stream_Write_UINT32(s, deviceId); /* DeviceId (4 bytes) */
+ Stream_Write_UINT32(s, fileId); /* FileId (4 bytes) */
+ Stream_Write_UINT32(s, completionId); /* CompletionId (4 bytes) */
+ Stream_Write_UINT32(s, majorFunction); /* MajorFunction (4 bytes) */
+ Stream_Write_UINT32(s, minorFunction); /* MinorFunction (4 bytes) */
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_read_file_directory_information(wLog* log, wStream* s,
+ FILE_DIRECTORY_INFORMATION* fdi)
+{
+ UINT32 fileNameLength = 0;
+ WINPR_ASSERT(fdi);
+ ZeroMemory(fdi, sizeof(FILE_DIRECTORY_INFORMATION));
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 64))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, fdi->NextEntryOffset); /* NextEntryOffset (4 bytes) */
+ Stream_Read_UINT32(s, fdi->FileIndex); /* FileIndex (4 bytes) */
+ Stream_Read_INT64(s, fdi->CreationTime.QuadPart); /* CreationTime (8 bytes) */
+ Stream_Read_INT64(s, fdi->LastAccessTime.QuadPart); /* LastAccessTime (8 bytes) */
+ Stream_Read_INT64(s, fdi->LastWriteTime.QuadPart); /* LastWriteTime (8 bytes) */
+ Stream_Read_INT64(s, fdi->ChangeTime.QuadPart); /* ChangeTime (8 bytes) */
+ Stream_Read_INT64(s, fdi->EndOfFile.QuadPart); /* EndOfFile (8 bytes) */
+ Stream_Read_INT64(s, fdi->AllocationSize.QuadPart); /* AllocationSize (8 bytes) */
+ Stream_Read_UINT32(s, fdi->FileAttributes); /* FileAttributes (4 bytes) */
+ Stream_Read_UINT32(s, fileNameLength); /* FileNameLength (4 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, fileNameLength))
+ return ERROR_INVALID_DATA;
+
+ if (Stream_Read_UTF16_String_As_UTF8_Buffer(s, fileNameLength / sizeof(WCHAR), fdi->FileName,
+ ARRAYSIZE(fdi->FileName)) < 0)
+ return ERROR_INVALID_DATA;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_send_device_create_request(RdpdrServerContext* context, UINT32 deviceId,
+ UINT32 completionId, const char* path,
+ UINT32 desiredAccess, UINT32 createOptions,
+ UINT32 createDisposition)
+{
+ size_t pathLength = 0;
+ wStream* s = NULL;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerSendDeviceCreateRequest: deviceId=%" PRIu32
+ ", path=%s, desiredAccess=0x%" PRIx32 " createOptions=0x%" PRIx32
+ " createDisposition=0x%" PRIx32 "",
+ deviceId, path, desiredAccess, createOptions, createDisposition);
+ /* Compute the required Unicode size. */
+ pathLength = (strlen(path) + 1U) * sizeof(WCHAR);
+ s = Stream_New(NULL, 256U + pathLength);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rdpdr_server_write_device_iorequest(s, deviceId, 0, completionId, IRP_MJ_CREATE, 0);
+ Stream_Write_UINT32(s, desiredAccess); /* DesiredAccess (4 bytes) */
+ Stream_Write_UINT32(s, 0); /* AllocationSize (8 bytes) */
+ Stream_Write_UINT32(s, 0);
+ Stream_Write_UINT32(s, 0); /* FileAttributes (4 bytes) */
+ Stream_Write_UINT32(s, 3); /* SharedAccess (4 bytes) */
+ Stream_Write_UINT32(s, createDisposition); /* CreateDisposition (4 bytes) */
+ Stream_Write_UINT32(s, createOptions); /* CreateOptions (4 bytes) */
+ WINPR_ASSERT(pathLength <= UINT32_MAX);
+ Stream_Write_UINT32(s, (UINT32)pathLength); /* PathLength (4 bytes) */
+ /* Convert the path to Unicode. */
+ if (Stream_Write_UTF16_String_From_UTF8(s, pathLength / sizeof(WCHAR), path,
+ pathLength / sizeof(WCHAR), TRUE) < 0)
+ return ERROR_INTERNAL_ERROR;
+ return rdpdr_seal_send_free_request(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_send_device_close_request(RdpdrServerContext* context, UINT32 deviceId,
+ UINT32 fileId, UINT32 completionId)
+{
+ wStream* s = NULL;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerSendDeviceCloseRequest: deviceId=%" PRIu32 ", fileId=%" PRIu32 "",
+ deviceId, fileId);
+ s = Stream_New(NULL, 128);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, IRP_MJ_CLOSE, 0);
+ Stream_Zero(s, 32); /* Padding (32 bytes) */
+ return rdpdr_seal_send_free_request(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_send_device_read_request(RdpdrServerContext* context, UINT32 deviceId,
+ UINT32 fileId, UINT32 completionId, UINT32 length,
+ UINT32 offset)
+{
+ wStream* s = NULL;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerSendDeviceReadRequest: deviceId=%" PRIu32 ", fileId=%" PRIu32
+ ", length=%" PRIu32 ", offset=%" PRIu32 "",
+ deviceId, fileId, length, offset);
+ s = Stream_New(NULL, 128);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, IRP_MJ_READ, 0);
+ Stream_Write_UINT32(s, length); /* Length (4 bytes) */
+ Stream_Write_UINT32(s, offset); /* Offset (8 bytes) */
+ Stream_Write_UINT32(s, 0);
+ Stream_Zero(s, 20); /* Padding (20 bytes) */
+ return rdpdr_seal_send_free_request(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_send_device_write_request(RdpdrServerContext* context, UINT32 deviceId,
+ UINT32 fileId, UINT32 completionId,
+ const char* data, UINT32 length, UINT32 offset)
+{
+ wStream* s = NULL;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerSendDeviceWriteRequest: deviceId=%" PRIu32 ", fileId=%" PRIu32
+ ", length=%" PRIu32 ", offset=%" PRIu32 "",
+ deviceId, fileId, length, offset);
+ s = Stream_New(NULL, 64 + length);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, IRP_MJ_WRITE, 0);
+ Stream_Write_UINT32(s, length); /* Length (4 bytes) */
+ Stream_Write_UINT32(s, offset); /* Offset (8 bytes) */
+ Stream_Write_UINT32(s, 0);
+ Stream_Zero(s, 20); /* Padding (20 bytes) */
+ Stream_Write(s, data, length); /* WriteData (variable) */
+ return rdpdr_seal_send_free_request(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_send_device_query_directory_request(RdpdrServerContext* context,
+ UINT32 deviceId, UINT32 fileId,
+ UINT32 completionId, const char* path)
+{
+ size_t pathLength = 0;
+ wStream* s = NULL;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerSendDeviceQueryDirectoryRequest: deviceId=%" PRIu32 ", fileId=%" PRIu32
+ ", path=%s",
+ deviceId, fileId, path);
+ /* Compute the required Unicode size. */
+ pathLength = path ? (strlen(path) + 1) * sizeof(WCHAR) : 0;
+ s = Stream_New(NULL, 64 + pathLength);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, IRP_MJ_DIRECTORY_CONTROL,
+ IRP_MN_QUERY_DIRECTORY);
+ Stream_Write_UINT32(s, FileDirectoryInformation); /* FsInformationClass (4 bytes) */
+ Stream_Write_UINT8(s, path ? 1 : 0); /* InitialQuery (1 byte) */
+ WINPR_ASSERT(pathLength <= UINT32_MAX);
+ Stream_Write_UINT32(s, (UINT32)pathLength); /* PathLength (4 bytes) */
+ Stream_Zero(s, 23); /* Padding (23 bytes) */
+
+ /* Convert the path to Unicode. */
+ if (pathLength > 0)
+ {
+ if (Stream_Write_UTF16_String_From_UTF8(s, pathLength / sizeof(WCHAR), path,
+ pathLength / sizeof(WCHAR), TRUE) < 0)
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return rdpdr_seal_send_free_request(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_send_device_file_rename_request(RdpdrServerContext* context,
+ UINT32 deviceId, UINT32 fileId,
+ UINT32 completionId, const char* path)
+{
+ size_t pathLength = 0;
+ wStream* s = NULL;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerSendDeviceFileNameRequest: deviceId=%" PRIu32 ", fileId=%" PRIu32
+ ", path=%s",
+ deviceId, fileId, path);
+ /* Compute the required Unicode size. */
+ pathLength = path ? (strlen(path) + 1) * sizeof(WCHAR) : 0;
+ s = Stream_New(NULL, 64 + pathLength);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, IRP_MJ_SET_INFORMATION,
+ 0);
+ Stream_Write_UINT32(s, FileRenameInformation); /* FsInformationClass (4 bytes) */
+ WINPR_ASSERT(pathLength <= UINT32_MAX - 6U);
+ Stream_Write_UINT32(s, (UINT32)pathLength + 6U); /* Length (4 bytes) */
+ Stream_Zero(s, 24); /* Padding (24 bytes) */
+ /* RDP_FILE_RENAME_INFORMATION */
+ Stream_Write_UINT8(s, 0); /* ReplaceIfExists (1 byte) */
+ Stream_Write_UINT8(s, 0); /* RootDirectory (1 byte) */
+ Stream_Write_UINT32(s, (UINT32)pathLength); /* FileNameLength (4 bytes) */
+
+ /* Convert the path to Unicode. */
+ if (pathLength > 0)
+ {
+ if (Stream_Write_UTF16_String_From_UTF8(s, pathLength / sizeof(WCHAR), path,
+ pathLength / sizeof(WCHAR), TRUE) < 0)
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return rdpdr_seal_send_free_request(context, s);
+}
+
+static void rdpdr_server_convert_slashes(char* path, int size)
+{
+ WINPR_ASSERT(path || (size <= 0));
+
+ for (int i = 0; (i < size) && (path[i] != '\0'); i++)
+ {
+ if (path[i] == '/')
+ path[i] = '\\';
+ }
+}
+
+/*************************************************
+ * Drive Create Directory
+ ************************************************/
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_create_directory_callback2(RdpdrServerContext* context, wStream* s,
+ RDPDR_IRP* irp, UINT32 deviceId,
+ UINT32 completionId, UINT32 ioStatus)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(irp);
+ WINPR_UNUSED(s);
+
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerDriveCreateDirectoryCallback2: deviceId=%" PRIu32
+ ", completionId=%" PRIu32 ", ioStatus=0x%" PRIx32 "",
+ deviceId, completionId, ioStatus);
+ /* Invoke the create directory completion routine. */
+ context->OnDriveCreateDirectoryComplete(context, irp->CallbackData, ioStatus);
+ /* Destroy the IRP. */
+ rdpdr_server_irp_free(irp);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_create_directory_callback1(RdpdrServerContext* context, wStream* s,
+ RDPDR_IRP* irp, UINT32 deviceId,
+ UINT32 completionId, UINT32 ioStatus)
+{
+ UINT32 fileId = 0;
+ UINT8 information = 0;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(irp);
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerDriveCreateDirectoryCallback1: deviceId=%" PRIu32
+ ", completionId=%" PRIu32 ", ioStatus=0x%" PRIx32 "",
+ deviceId, completionId, ioStatus);
+
+ if (ioStatus != STATUS_SUCCESS)
+ {
+ /* Invoke the create directory completion routine. */
+ context->OnDriveCreateDirectoryComplete(context, irp->CallbackData, ioStatus);
+ /* Destroy the IRP. */
+ rdpdr_server_irp_free(irp);
+ return CHANNEL_RC_OK;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, fileId); /* FileId (4 bytes) */
+ Stream_Read_UINT8(s, information); /* Information (1 byte) */
+ /* Setup the IRP. */
+ irp->CompletionId = context->priv->NextCompletionId++;
+ irp->Callback = rdpdr_server_drive_create_directory_callback2;
+ irp->DeviceId = deviceId;
+ irp->FileId = fileId;
+
+ if (!rdpdr_server_enqueue_irp(context, irp))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!");
+ rdpdr_server_irp_free(irp);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Send a request to close the file */
+ return rdpdr_server_send_device_close_request(context, deviceId, fileId, irp->CompletionId);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_create_directory(RdpdrServerContext* context, void* callbackData,
+ UINT32 deviceId, const char* path)
+{
+ RDPDR_IRP* irp = NULL;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(callbackData);
+ WINPR_ASSERT(path);
+ irp = rdpdr_server_irp_new();
+
+ if (!irp)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ irp->CompletionId = context->priv->NextCompletionId++;
+ irp->Callback = rdpdr_server_drive_create_directory_callback1;
+ irp->CallbackData = callbackData;
+ irp->DeviceId = deviceId;
+ strncpy(irp->PathName, path, sizeof(irp->PathName) - 1);
+ rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName));
+
+ if (!rdpdr_server_enqueue_irp(context, irp))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!");
+ rdpdr_server_irp_free(irp);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Send a request to open the file. */
+ return rdpdr_server_send_device_create_request(
+ context, deviceId, irp->CompletionId, irp->PathName, FILE_READ_DATA | SYNCHRONIZE,
+ FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATE);
+}
+
+/*************************************************
+ * Drive Delete Directory
+ ************************************************/
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_delete_directory_callback2(RdpdrServerContext* context, wStream* s,
+ RDPDR_IRP* irp, UINT32 deviceId,
+ UINT32 completionId, UINT32 ioStatus)
+{
+ WINPR_UNUSED(s);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerDriveDeleteDirectoryCallback2: deviceId=%" PRIu32
+ ", completionId=%" PRIu32 ", ioStatus=0x%" PRIx32 "",
+ deviceId, completionId, ioStatus);
+ /* Invoke the delete directory completion routine. */
+ context->OnDriveDeleteDirectoryComplete(context, irp->CallbackData, ioStatus);
+ /* Destroy the IRP. */
+ rdpdr_server_irp_free(irp);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_delete_directory_callback1(RdpdrServerContext* context, wStream* s,
+ RDPDR_IRP* irp, UINT32 deviceId,
+ UINT32 completionId, UINT32 ioStatus)
+{
+ UINT32 fileId = 0;
+ UINT8 information = 0;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerDriveDeleteDirectoryCallback1: deviceId=%" PRIu32
+ ", completionId=%" PRIu32 ", ioStatus=0x%" PRIx32 "",
+ deviceId, completionId, ioStatus);
+
+ if (ioStatus != STATUS_SUCCESS)
+ {
+ /* Invoke the delete directory completion routine. */
+ context->OnDriveDeleteFileComplete(context, irp->CallbackData, ioStatus);
+ /* Destroy the IRP. */
+ rdpdr_server_irp_free(irp);
+ return CHANNEL_RC_OK;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, fileId); /* FileId (4 bytes) */
+ Stream_Read_UINT8(s, information); /* Information (1 byte) */
+ /* Setup the IRP. */
+ irp->CompletionId = context->priv->NextCompletionId++;
+ irp->Callback = rdpdr_server_drive_delete_directory_callback2;
+ irp->DeviceId = deviceId;
+ irp->FileId = fileId;
+
+ if (!rdpdr_server_enqueue_irp(context, irp))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!");
+ rdpdr_server_irp_free(irp);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Send a request to close the file */
+ return rdpdr_server_send_device_close_request(context, deviceId, fileId, irp->CompletionId);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_delete_directory(RdpdrServerContext* context, void* callbackData,
+ UINT32 deviceId, const char* path)
+{
+ RDPDR_IRP* irp = rdpdr_server_irp_new();
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+
+ if (!irp)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ irp->CompletionId = context->priv->NextCompletionId++;
+ irp->Callback = rdpdr_server_drive_delete_directory_callback1;
+ irp->CallbackData = callbackData;
+ irp->DeviceId = deviceId;
+ strncpy(irp->PathName, path, sizeof(irp->PathName) - 1);
+ rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName));
+
+ if (!rdpdr_server_enqueue_irp(context, irp))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!");
+ rdpdr_server_irp_free(irp);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Send a request to open the file. */
+ return rdpdr_server_send_device_create_request(
+ context, deviceId, irp->CompletionId, irp->PathName, DELETE | SYNCHRONIZE,
+ FILE_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPEN);
+}
+
+/*************************************************
+ * Drive Query Directory
+ ************************************************/
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_query_directory_callback2(RdpdrServerContext* context, wStream* s,
+ RDPDR_IRP* irp, UINT32 deviceId,
+ UINT32 completionId, UINT32 ioStatus)
+{
+ UINT error = 0;
+ UINT32 length = 0;
+ FILE_DIRECTORY_INFORMATION fdi = { 0 };
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerDriveQueryDirectoryCallback2: deviceId=%" PRIu32
+ ", completionId=%" PRIu32 ", ioStatus=0x%" PRIx32 "",
+ deviceId, completionId, ioStatus);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, length); /* Length (4 bytes) */
+
+ if (length > 0)
+ {
+ if ((error = rdpdr_server_read_file_directory_information(context->priv->log, s, &fdi)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpdr_server_read_file_directory_information failed with error %" PRIu32
+ "!",
+ error);
+ return error;
+ }
+ }
+ else
+ {
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 1))
+ return ERROR_INVALID_DATA;
+
+ Stream_Seek(s, 1); /* Padding (1 byte) */
+ }
+
+ if (ioStatus == STATUS_SUCCESS)
+ {
+ /* Invoke the query directory completion routine. */
+ context->OnDriveQueryDirectoryComplete(context, irp->CallbackData, ioStatus,
+ length > 0 ? &fdi : NULL);
+ /* Setup the IRP. */
+ irp->CompletionId = context->priv->NextCompletionId++;
+ irp->Callback = rdpdr_server_drive_query_directory_callback2;
+
+ if (!rdpdr_server_enqueue_irp(context, irp))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!");
+ rdpdr_server_irp_free(irp);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Send a request to query the directory. */
+ return rdpdr_server_send_device_query_directory_request(context, irp->DeviceId, irp->FileId,
+ irp->CompletionId, NULL);
+ }
+ else
+ {
+ /* Invoke the query directory completion routine. */
+ context->OnDriveQueryDirectoryComplete(context, irp->CallbackData, ioStatus, NULL);
+ /* Destroy the IRP. */
+ rdpdr_server_irp_free(irp);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_query_directory_callback1(RdpdrServerContext* context, wStream* s,
+ RDPDR_IRP* irp, UINT32 deviceId,
+ UINT32 completionId, UINT32 ioStatus)
+{
+ UINT32 fileId = 0;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerDriveQueryDirectoryCallback1: deviceId=%" PRIu32
+ ", completionId=%" PRIu32 ", ioStatus=0x%" PRIx32 "",
+ deviceId, completionId, ioStatus);
+
+ if (ioStatus != STATUS_SUCCESS)
+ {
+ /* Invoke the query directory completion routine. */
+ context->OnDriveQueryDirectoryComplete(context, irp->CallbackData, ioStatus, NULL);
+ /* Destroy the IRP. */
+ rdpdr_server_irp_free(irp);
+ return CHANNEL_RC_OK;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, fileId);
+ /* Setup the IRP. */
+ irp->CompletionId = context->priv->NextCompletionId++;
+ irp->Callback = rdpdr_server_drive_query_directory_callback2;
+ irp->DeviceId = deviceId;
+ irp->FileId = fileId;
+ winpr_str_append("\\*.*", irp->PathName, ARRAYSIZE(irp->PathName), NULL);
+
+ if (!rdpdr_server_enqueue_irp(context, irp))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!");
+ rdpdr_server_irp_free(irp);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Send a request to query the directory. */
+ return rdpdr_server_send_device_query_directory_request(context, deviceId, fileId,
+ irp->CompletionId, irp->PathName);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_query_directory(RdpdrServerContext* context, void* callbackData,
+ UINT32 deviceId, const char* path)
+{
+ RDPDR_IRP* irp = rdpdr_server_irp_new();
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+
+ if (!irp)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ irp->CompletionId = context->priv->NextCompletionId++;
+ irp->Callback = rdpdr_server_drive_query_directory_callback1;
+ irp->CallbackData = callbackData;
+ irp->DeviceId = deviceId;
+ strncpy(irp->PathName, path, sizeof(irp->PathName) - 1);
+ rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName));
+
+ if (!rdpdr_server_enqueue_irp(context, irp))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!");
+ rdpdr_server_irp_free(irp);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Send a request to open the directory. */
+ return rdpdr_server_send_device_create_request(
+ context, deviceId, irp->CompletionId, irp->PathName, FILE_READ_DATA | SYNCHRONIZE,
+ FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPEN);
+}
+
+/*************************************************
+ * Drive Open File
+ ************************************************/
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_open_file_callback(RdpdrServerContext* context, wStream* s,
+ RDPDR_IRP* irp, UINT32 deviceId,
+ UINT32 completionId, UINT32 ioStatus)
+{
+ UINT32 fileId = 0;
+ UINT8 information = 0;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerDriveOpenFileCallback: deviceId=%" PRIu32 ", completionId=%" PRIu32
+ ", ioStatus=0x%" PRIx32 "",
+ deviceId, completionId, ioStatus);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, fileId); /* FileId (4 bytes) */
+ Stream_Read_UINT8(s, information); /* Information (1 byte) */
+ /* Invoke the open file completion routine. */
+ context->OnDriveOpenFileComplete(context, irp->CallbackData, ioStatus, deviceId, fileId);
+ /* Destroy the IRP. */
+ rdpdr_server_irp_free(irp);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_open_file(RdpdrServerContext* context, void* callbackData,
+ UINT32 deviceId, const char* path, UINT32 desiredAccess,
+ UINT32 createDisposition)
+{
+ RDPDR_IRP* irp = rdpdr_server_irp_new();
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+
+ if (!irp)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ irp->CompletionId = context->priv->NextCompletionId++;
+ irp->Callback = rdpdr_server_drive_open_file_callback;
+ irp->CallbackData = callbackData;
+ irp->DeviceId = deviceId;
+ strncpy(irp->PathName, path, sizeof(irp->PathName) - 1);
+ rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName));
+
+ if (!rdpdr_server_enqueue_irp(context, irp))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!");
+ rdpdr_server_irp_free(irp);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Send a request to open the file. */
+ return rdpdr_server_send_device_create_request(context, deviceId, irp->CompletionId,
+ irp->PathName, desiredAccess | SYNCHRONIZE,
+ FILE_SYNCHRONOUS_IO_NONALERT, createDisposition);
+}
+
+/*************************************************
+ * Drive Read File
+ ************************************************/
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_read_file_callback(RdpdrServerContext* context, wStream* s,
+ RDPDR_IRP* irp, UINT32 deviceId,
+ UINT32 completionId, UINT32 ioStatus)
+{
+ UINT32 length = 0;
+ char* buffer = NULL;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerDriveReadFileCallback: deviceId=%" PRIu32 ", completionId=%" PRIu32
+ ", ioStatus=0x%" PRIx32 "",
+ deviceId, completionId, ioStatus);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, length); /* Length (4 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, length))
+ return ERROR_INVALID_DATA;
+
+ if (length > 0)
+ {
+ buffer = Stream_Pointer(s);
+ Stream_Seek(s, length);
+ }
+
+ /* Invoke the read file completion routine. */
+ context->OnDriveReadFileComplete(context, irp->CallbackData, ioStatus, buffer, length);
+ /* Destroy the IRP. */
+ rdpdr_server_irp_free(irp);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_read_file(RdpdrServerContext* context, void* callbackData,
+ UINT32 deviceId, UINT32 fileId, UINT32 length,
+ UINT32 offset)
+{
+ RDPDR_IRP* irp = rdpdr_server_irp_new();
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+
+ if (!irp)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ irp->CompletionId = context->priv->NextCompletionId++;
+ irp->Callback = rdpdr_server_drive_read_file_callback;
+ irp->CallbackData = callbackData;
+ irp->DeviceId = deviceId;
+ irp->FileId = fileId;
+
+ if (!rdpdr_server_enqueue_irp(context, irp))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!");
+ rdpdr_server_irp_free(irp);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Send a request to open the directory. */
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): rdpdr_server_enqueue_irp owns irp
+ return rdpdr_server_send_device_read_request(context, deviceId, fileId, irp->CompletionId,
+ length, offset);
+}
+
+/*************************************************
+ * Drive Write File
+ ************************************************/
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_write_file_callback(RdpdrServerContext* context, wStream* s,
+ RDPDR_IRP* irp, UINT32 deviceId,
+ UINT32 completionId, UINT32 ioStatus)
+{
+ UINT32 length = 0;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerDriveWriteFileCallback: deviceId=%" PRIu32 ", completionId=%" PRIu32
+ ", ioStatus=0x%" PRIx32 "",
+ deviceId, completionId, ioStatus);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, length); /* Length (4 bytes) */
+ Stream_Seek(s, 1); /* Padding (1 byte) */
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, length))
+ return ERROR_INVALID_DATA;
+
+ /* Invoke the write file completion routine. */
+ context->OnDriveWriteFileComplete(context, irp->CallbackData, ioStatus, length);
+ /* Destroy the IRP. */
+ rdpdr_server_irp_free(irp);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_write_file(RdpdrServerContext* context, void* callbackData,
+ UINT32 deviceId, UINT32 fileId, const char* buffer,
+ UINT32 length, UINT32 offset)
+{
+ RDPDR_IRP* irp = rdpdr_server_irp_new();
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+
+ if (!irp)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ irp->CompletionId = context->priv->NextCompletionId++;
+ irp->Callback = rdpdr_server_drive_write_file_callback;
+ irp->CallbackData = callbackData;
+ irp->DeviceId = deviceId;
+ irp->FileId = fileId;
+
+ if (!rdpdr_server_enqueue_irp(context, irp))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!");
+ rdpdr_server_irp_free(irp);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Send a request to open the directory. */
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): rdpdr_server_enqueue_irp owns irp
+ return rdpdr_server_send_device_write_request(context, deviceId, fileId, irp->CompletionId,
+ buffer, length, offset);
+}
+
+/*************************************************
+ * Drive Close File
+ ************************************************/
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_close_file_callback(RdpdrServerContext* context, wStream* s,
+ RDPDR_IRP* irp, UINT32 deviceId,
+ UINT32 completionId, UINT32 ioStatus)
+{
+ WINPR_UNUSED(s);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerDriveCloseFileCallback: deviceId=%" PRIu32 ", completionId=%" PRIu32
+ ", ioStatus=0x%" PRIx32 "",
+ deviceId, completionId, ioStatus);
+
+ // padding 5 bytes
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5))
+ return ERROR_INVALID_DATA;
+
+ Stream_Seek(s, 5);
+
+ /* Invoke the close file completion routine. */
+ context->OnDriveCloseFileComplete(context, irp->CallbackData, ioStatus);
+ /* Destroy the IRP. */
+ rdpdr_server_irp_free(irp);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_close_file(RdpdrServerContext* context, void* callbackData,
+ UINT32 deviceId, UINT32 fileId)
+{
+ RDPDR_IRP* irp = rdpdr_server_irp_new();
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+
+ if (!irp)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ irp->CompletionId = context->priv->NextCompletionId++;
+ irp->Callback = rdpdr_server_drive_close_file_callback;
+ irp->CallbackData = callbackData;
+ irp->DeviceId = deviceId;
+ irp->FileId = fileId;
+
+ if (!rdpdr_server_enqueue_irp(context, irp))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!");
+ rdpdr_server_irp_free(irp);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Send a request to open the directory. */
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): rdpdr_server_enqueue_irp owns irp
+ return rdpdr_server_send_device_close_request(context, deviceId, fileId, irp->CompletionId);
+}
+
+/*************************************************
+ * Drive Delete File
+ ************************************************/
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_delete_file_callback2(RdpdrServerContext* context, wStream* s,
+ RDPDR_IRP* irp, UINT32 deviceId,
+ UINT32 completionId, UINT32 ioStatus)
+{
+ WINPR_UNUSED(s);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerDriveDeleteFileCallback2: deviceId=%" PRIu32 ", completionId=%" PRIu32
+ ", ioStatus=0x%" PRIx32 "",
+ deviceId, completionId, ioStatus);
+ /* Invoke the delete file completion routine. */
+ context->OnDriveDeleteFileComplete(context, irp->CallbackData, ioStatus);
+ /* Destroy the IRP. */
+ rdpdr_server_irp_free(irp);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_delete_file_callback1(RdpdrServerContext* context, wStream* s,
+ RDPDR_IRP* irp, UINT32 deviceId,
+ UINT32 completionId, UINT32 ioStatus)
+{
+ UINT32 fileId = 0;
+ UINT8 information = 0;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerDriveDeleteFileCallback1: deviceId=%" PRIu32 ", completionId=%" PRIu32
+ ", ioStatus=0x%" PRIx32 "",
+ deviceId, completionId, ioStatus);
+
+ if (ioStatus != STATUS_SUCCESS)
+ {
+ /* Invoke the close file completion routine. */
+ context->OnDriveDeleteFileComplete(context, irp->CallbackData, ioStatus);
+ /* Destroy the IRP. */
+ rdpdr_server_irp_free(irp);
+ return CHANNEL_RC_OK;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, fileId); /* FileId (4 bytes) */
+ Stream_Read_UINT8(s, information); /* Information (1 byte) */
+ /* Setup the IRP. */
+ irp->CompletionId = context->priv->NextCompletionId++;
+ irp->Callback = rdpdr_server_drive_delete_file_callback2;
+ irp->DeviceId = deviceId;
+ irp->FileId = fileId;
+
+ if (!rdpdr_server_enqueue_irp(context, irp))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!");
+ rdpdr_server_irp_free(irp);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Send a request to close the file */
+ return rdpdr_server_send_device_close_request(context, deviceId, fileId, irp->CompletionId);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_delete_file(RdpdrServerContext* context, void* callbackData,
+ UINT32 deviceId, const char* path)
+{
+ RDPDR_IRP* irp = rdpdr_server_irp_new();
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+
+ if (!irp)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ irp->CompletionId = context->priv->NextCompletionId++;
+ irp->Callback = rdpdr_server_drive_delete_file_callback1;
+ irp->CallbackData = callbackData;
+ irp->DeviceId = deviceId;
+ strncpy(irp->PathName, path, sizeof(irp->PathName) - 1);
+ rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName));
+
+ if (!rdpdr_server_enqueue_irp(context, irp))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!");
+ rdpdr_server_irp_free(irp);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Send a request to open the file. */
+ return rdpdr_server_send_device_create_request(
+ context, deviceId, irp->CompletionId, irp->PathName, FILE_READ_DATA | SYNCHRONIZE,
+ FILE_DELETE_ON_CLOSE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPEN);
+}
+
+/*************************************************
+ * Drive Rename File
+ ************************************************/
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_rename_file_callback3(RdpdrServerContext* context, wStream* s,
+ RDPDR_IRP* irp, UINT32 deviceId,
+ UINT32 completionId, UINT32 ioStatus)
+{
+ WINPR_UNUSED(context);
+ WINPR_UNUSED(s);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerDriveRenameFileCallback3: deviceId=%" PRIu32 ", completionId=%" PRIu32
+ ", ioStatus=0x%" PRIx32 "",
+ deviceId, completionId, ioStatus);
+ /* Destroy the IRP. */
+ rdpdr_server_irp_free(irp);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_rename_file_callback2(RdpdrServerContext* context, wStream* s,
+ RDPDR_IRP* irp, UINT32 deviceId,
+ UINT32 completionId, UINT32 ioStatus)
+{
+ UINT32 length = 0;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerDriveRenameFileCallback2: deviceId=%" PRIu32 ", completionId=%" PRIu32
+ ", ioStatus=0x%" PRIx32 "",
+ deviceId, completionId, ioStatus);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, length); /* Length (4 bytes) */
+ Stream_Seek(s, 1); /* Padding (1 byte) */
+ /* Invoke the rename file completion routine. */
+ context->OnDriveRenameFileComplete(context, irp->CallbackData, ioStatus);
+ /* Setup the IRP. */
+ irp->CompletionId = context->priv->NextCompletionId++;
+ irp->Callback = rdpdr_server_drive_rename_file_callback3;
+ irp->DeviceId = deviceId;
+
+ if (!rdpdr_server_enqueue_irp(context, irp))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!");
+ rdpdr_server_irp_free(irp);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Send a request to close the file */
+ return rdpdr_server_send_device_close_request(context, deviceId, irp->FileId,
+ irp->CompletionId);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_rename_file_callback1(RdpdrServerContext* context, wStream* s,
+ RDPDR_IRP* irp, UINT32 deviceId,
+ UINT32 completionId, UINT32 ioStatus)
+{
+ UINT32 fileId = 0;
+ UINT8 information = 0;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(irp);
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RdpdrServerDriveRenameFileCallback1: deviceId=%" PRIu32 ", completionId=%" PRIu32
+ ", ioStatus=0x%" PRIx32 "",
+ deviceId, completionId, ioStatus);
+
+ if (ioStatus != STATUS_SUCCESS)
+ {
+ /* Invoke the rename file completion routine. */
+ context->OnDriveRenameFileComplete(context, irp->CallbackData, ioStatus);
+ /* Destroy the IRP. */
+ rdpdr_server_irp_free(irp);
+ return CHANNEL_RC_OK;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, fileId); /* FileId (4 bytes) */
+ Stream_Read_UINT8(s, information); /* Information (1 byte) */
+ /* Setup the IRP. */
+ irp->CompletionId = context->priv->NextCompletionId++;
+ irp->Callback = rdpdr_server_drive_rename_file_callback2;
+ irp->DeviceId = deviceId;
+ irp->FileId = fileId;
+
+ if (!rdpdr_server_enqueue_irp(context, irp))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!");
+ rdpdr_server_irp_free(irp);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Send a request to rename the file */
+ return rdpdr_server_send_device_file_rename_request(context, deviceId, fileId,
+ irp->CompletionId, irp->ExtraBuffer);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpdr_server_drive_rename_file(RdpdrServerContext* context, void* callbackData,
+ UINT32 deviceId, const char* oldPath,
+ const char* newPath)
+{
+ RDPDR_IRP* irp = NULL;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ irp = rdpdr_server_irp_new();
+
+ if (!irp)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ irp->CompletionId = context->priv->NextCompletionId++;
+ irp->Callback = rdpdr_server_drive_rename_file_callback1;
+ irp->CallbackData = callbackData;
+ irp->DeviceId = deviceId;
+ strncpy(irp->PathName, oldPath, sizeof(irp->PathName) - 1);
+ strncpy(irp->ExtraBuffer, newPath, sizeof(irp->ExtraBuffer) - 1);
+ rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName));
+ rdpdr_server_convert_slashes(irp->ExtraBuffer, sizeof(irp->ExtraBuffer));
+
+ if (!rdpdr_server_enqueue_irp(context, irp))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!");
+ rdpdr_server_irp_free(irp);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ /* Send a request to open the file. */
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): rdpdr_server_enqueue_irp owns irp
+ return rdpdr_server_send_device_create_request(context, deviceId, irp->CompletionId,
+ irp->PathName, FILE_READ_DATA | SYNCHRONIZE,
+ FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPEN);
+}
+
+static void rdpdr_server_private_free(RdpdrServerPrivate* ctx)
+{
+ if (!ctx)
+ return;
+ ListDictionary_Free(ctx->IrpList);
+ HashTable_Free(ctx->devicelist);
+ free(ctx->ClientComputerName);
+ free(ctx);
+}
+
+#define TAG CHANNELS_TAG("rdpdr.server")
+static RdpdrServerPrivate* rdpdr_server_private_new(void)
+{
+ RdpdrServerPrivate* priv = (RdpdrServerPrivate*)calloc(1, sizeof(RdpdrServerPrivate));
+
+ if (!priv)
+ goto fail;
+
+ priv->log = WLog_Get(TAG);
+ priv->VersionMajor = RDPDR_VERSION_MAJOR;
+ priv->VersionMinor = RDPDR_VERSION_MINOR_RDP6X;
+ priv->ClientId = g_ClientId++;
+ priv->UserLoggedOnPdu = TRUE;
+ priv->NextCompletionId = 1;
+ priv->IrpList = ListDictionary_New(TRUE);
+
+ if (!priv->IrpList)
+ goto fail;
+
+ priv->devicelist = HashTable_New(FALSE);
+ if (!priv->devicelist)
+ goto fail;
+
+ HashTable_SetHashFunction(priv->devicelist, rdpdr_deviceid_hash);
+ wObject* obj = HashTable_ValueObject(priv->devicelist);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = rdpdr_device_free_h;
+ obj->fnObjectNew = rdpdr_device_clone;
+
+ return priv;
+fail:
+ rdpdr_server_private_free(priv);
+ return NULL;
+}
+
+RdpdrServerContext* rdpdr_server_context_new(HANDLE vcm)
+{
+ RdpdrServerContext* context = (RdpdrServerContext*)calloc(1, sizeof(RdpdrServerContext));
+
+ if (!context)
+ goto fail;
+
+ context->vcm = vcm;
+ context->Start = rdpdr_server_start;
+ context->Stop = rdpdr_server_stop;
+ context->DriveCreateDirectory = rdpdr_server_drive_create_directory;
+ context->DriveDeleteDirectory = rdpdr_server_drive_delete_directory;
+ context->DriveQueryDirectory = rdpdr_server_drive_query_directory;
+ context->DriveOpenFile = rdpdr_server_drive_open_file;
+ context->DriveReadFile = rdpdr_server_drive_read_file;
+ context->DriveWriteFile = rdpdr_server_drive_write_file;
+ context->DriveCloseFile = rdpdr_server_drive_close_file;
+ context->DriveDeleteFile = rdpdr_server_drive_delete_file;
+ context->DriveRenameFile = rdpdr_server_drive_rename_file;
+ context->priv = rdpdr_server_private_new();
+ if (!context->priv)
+ goto fail;
+
+ /* By default announce everything, the server application can deactivate that later on */
+ context->supported = UINT16_MAX;
+
+ return context;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ rdpdr_server_context_free(context);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void rdpdr_server_context_free(RdpdrServerContext* context)
+{
+ if (!context)
+ return;
+
+ rdpdr_server_private_free(context->priv);
+ free(context);
+}
diff --git a/channels/rdpdr/server/rdpdr_main.h b/channels/rdpdr/server/rdpdr_main.h
new file mode 100644
index 0000000..dabbae2
--- /dev/null
+++ b/channels/rdpdr/server/rdpdr_main.h
@@ -0,0 +1,47 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Device Redirection Virtual Channel Extension
+ *
+ * Copyright 2014 Dell Software <Mike.McDonald@software.dell.com>
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPDR_SERVER_MAIN_H
+#define FREERDP_CHANNEL_RDPDR_SERVER_MAIN_H
+
+#include <winpr/collections.h>
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+#include <freerdp/settings.h>
+#include <freerdp/server/rdpdr.h>
+
+typedef struct S_RDPDR_IRP
+{
+ UINT32 CompletionId;
+ UINT32 DeviceId;
+ UINT32 FileId;
+ char PathName[256];
+ char ExtraBuffer[256];
+ void* CallbackData;
+ UINT(*Callback)
+ (RdpdrServerContext* context, wStream* s, struct S_RDPDR_IRP* irp, UINT32 deviceId,
+ UINT32 completionId, UINT32 ioStatus);
+} RDPDR_IRP;
+
+#endif /* FREERDP_CHANNEL_RDPDR_SERVER_MAIN_H */
diff --git a/channels/rdpecam/CMakeLists.txt b/channels/rdpecam/CMakeLists.txt
new file mode 100644
index 0000000..63ed410
--- /dev/null
+++ b/channels/rdpecam/CMakeLists.txt
@@ -0,0 +1,22 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2022 Pascal Nowack <Pascal.Nowack@gmx.de>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("rdpecam")
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/rdpecam/ChannelOptions.cmake b/channels/rdpecam/ChannelOptions.cmake
new file mode 100644
index 0000000..7528d11
--- /dev/null
+++ b/channels/rdpecam/ChannelOptions.cmake
@@ -0,0 +1,12 @@
+
+set(OPTION_DEFAULT ON)
+set(OPTION_CLIENT_DEFAULT OFF)
+set(OPTION_SERVER_DEFAULT ON)
+
+define_channel_options(NAME "rdpecam" TYPE "dynamic"
+ DESCRIPTION "Video Capture Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPECAM]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/rdpecam/server/CMakeLists.txt b/channels/rdpecam/server/CMakeLists.txt
new file mode 100644
index 0000000..2f82428
--- /dev/null
+++ b/channels/rdpecam/server/CMakeLists.txt
@@ -0,0 +1,29 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2022 Pascal Nowack <Pascal.Nowack@gmx.de>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_server("rdpecam")
+
+set(${MODULE_PREFIX}_SRCS
+ camera_device_enumerator_main.c
+ camera_device_main.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry")
diff --git a/channels/rdpecam/server/camera_device_enumerator_main.c b/channels/rdpecam/server/camera_device_enumerator_main.c
new file mode 100644
index 0000000..27523b0
--- /dev/null
+++ b/channels/rdpecam/server/camera_device_enumerator_main.c
@@ -0,0 +1,611 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Capture Virtual Channel Extension
+ *
+ * Copyright 2022 Pascal Nowack <Pascal.Nowack@gmx.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/server/rdpecam-enumerator.h>
+
+#define TAG CHANNELS_TAG("rdpecam-enumerator.server")
+
+typedef enum
+{
+ ENUMERATOR_INITIAL,
+ ENUMERATOR_OPENED,
+} eEnumeratorChannelState;
+
+typedef struct
+{
+ CamDevEnumServerContext context;
+
+ HANDLE stopEvent;
+
+ HANDLE thread;
+ void* enumerator_channel;
+
+ DWORD SessionId;
+
+ BOOL isOpened;
+ BOOL externalThread;
+
+ /* Channel state */
+ eEnumeratorChannelState state;
+
+ wStream* buffer;
+} enumerator_server;
+
+static UINT enumerator_server_initialize(CamDevEnumServerContext* context, BOOL externalThread)
+{
+ UINT error = CHANNEL_RC_OK;
+ enumerator_server* enumerator = (enumerator_server*)context;
+
+ WINPR_ASSERT(enumerator);
+
+ if (enumerator->isOpened)
+ {
+ WLog_WARN(TAG, "Application error: Camera Device Enumerator channel already initialized, "
+ "calling in this state is not possible!");
+ return ERROR_INVALID_STATE;
+ }
+
+ enumerator->externalThread = externalThread;
+
+ return error;
+}
+
+static UINT enumerator_server_open_channel(enumerator_server* enumerator)
+{
+ CamDevEnumServerContext* context = &enumerator->context;
+ DWORD Error = ERROR_SUCCESS;
+ HANDLE hEvent = NULL;
+ DWORD BytesReturned = 0;
+ PULONG pSessionId = NULL;
+ UINT32 channelId = 0;
+ BOOL status = TRUE;
+
+ WINPR_ASSERT(enumerator);
+
+ if (WTSQuerySessionInformationA(enumerator->context.vcm, WTS_CURRENT_SESSION, WTSSessionId,
+ (LPSTR*)&pSessionId, &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSQuerySessionInformationA failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ enumerator->SessionId = (DWORD)*pSessionId;
+ WTSFreeMemory(pSessionId);
+ hEvent = WTSVirtualChannelManagerGetEventHandle(enumerator->context.vcm);
+
+ if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED)
+ {
+ Error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error);
+ return Error;
+ }
+
+ enumerator->enumerator_channel = WTSVirtualChannelOpenEx(
+ enumerator->SessionId, RDPECAM_CONTROL_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC);
+ if (!enumerator->enumerator_channel)
+ {
+ Error = GetLastError();
+ WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", Error);
+ return Error;
+ }
+
+ channelId = WTSChannelGetIdByHandle(enumerator->enumerator_channel);
+
+ IFCALLRET(context->ChannelIdAssigned, status, context, channelId);
+ if (!status)
+ {
+ WLog_ERR(TAG, "context->ChannelIdAssigned failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return Error;
+}
+
+static UINT enumerator_server_handle_select_version_request(CamDevEnumServerContext* context,
+ wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_SELECT_VERSION_REQUEST pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ IFCALLRET(context->SelectVersionRequest, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->SelectVersionRequest failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT enumerator_server_recv_device_added_notification(CamDevEnumServerContext* context,
+ wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_DEVICE_ADDED_NOTIFICATION pdu;
+ UINT error = CHANNEL_RC_OK;
+ size_t remaining_length = 0;
+ WCHAR* channel_name_start = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ /*
+ * RequiredLength 4:
+ *
+ * Nullterminator DeviceName (2),
+ * VirtualChannelName (>= 1),
+ * Nullterminator VirtualChannelName (1)
+ */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_NO_DATA;
+
+ pdu.DeviceName = Stream_Pointer(s);
+
+ remaining_length = Stream_GetRemainingLength(s);
+ channel_name_start = Stream_Pointer(s);
+
+ /* Search for null terminator of DeviceName */
+ size_t i = 0;
+ for (; i < remaining_length; i += sizeof(WCHAR), ++channel_name_start)
+ {
+ if (*channel_name_start == L'\0')
+ break;
+ }
+
+ if (*channel_name_start != L'\0')
+ {
+ WLog_ERR(TAG, "enumerator_server_recv_device_added_notification: Invalid DeviceName!");
+ return ERROR_INVALID_DATA;
+ }
+
+ pdu.VirtualChannelName = (char*)++channel_name_start;
+ ++i;
+
+ if (i >= remaining_length || *pdu.VirtualChannelName == '\0')
+ {
+ WLog_ERR(TAG,
+ "enumerator_server_recv_device_added_notification: Invalid VirtualChannelName!");
+ return ERROR_INVALID_DATA;
+ }
+
+ char* tmp = pdu.VirtualChannelName;
+ for (; i < remaining_length; ++i, ++tmp)
+ {
+ if (*tmp == '\0')
+ break;
+ }
+
+ if (*tmp != '\0')
+ {
+ WLog_ERR(TAG,
+ "enumerator_server_recv_device_added_notification: Invalid VirtualChannelName!");
+ return ERROR_INVALID_DATA;
+ }
+
+ IFCALLRET(context->DeviceAddedNotification, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->DeviceAddedNotification failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT enumerator_server_recv_device_removed_notification(CamDevEnumServerContext* context,
+ wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_DEVICE_REMOVED_NOTIFICATION pdu;
+ UINT error = CHANNEL_RC_OK;
+ size_t remaining_length = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return ERROR_NO_DATA;
+
+ pdu.VirtualChannelName = Stream_Pointer(s);
+
+ remaining_length = Stream_GetRemainingLength(s);
+ char* tmp = pdu.VirtualChannelName + 1;
+
+ for (size_t i = 1; i < remaining_length; ++i, ++tmp)
+ {
+ if (*tmp == '\0')
+ break;
+ }
+
+ if (*tmp != '\0')
+ {
+ WLog_ERR(TAG,
+ "enumerator_server_recv_device_removed_notification: Invalid VirtualChannelName!");
+ return ERROR_INVALID_DATA;
+ }
+
+ IFCALLRET(context->DeviceRemovedNotification, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->DeviceRemovedNotification failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT enumerator_process_message(enumerator_server* enumerator)
+{
+ BOOL rc = 0;
+ UINT error = ERROR_INTERNAL_ERROR;
+ ULONG BytesReturned = 0;
+ CAM_SHARED_MSG_HEADER header = { 0 };
+ wStream* s = NULL;
+
+ WINPR_ASSERT(enumerator);
+ WINPR_ASSERT(enumerator->enumerator_channel);
+
+ s = enumerator->buffer;
+ WINPR_ASSERT(s);
+
+ Stream_SetPosition(s, 0);
+ rc = WTSVirtualChannelRead(enumerator->enumerator_channel, 0, NULL, 0, &BytesReturned);
+ if (!rc)
+ goto out;
+
+ if (BytesReturned < 1)
+ {
+ error = CHANNEL_RC_OK;
+ goto out;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ if (WTSVirtualChannelRead(enumerator->enumerator_channel, 0, (PCHAR)Stream_Buffer(s),
+ (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ goto out;
+ }
+
+ Stream_SetLength(s, BytesReturned);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, CAM_HEADER_SIZE))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT8(s, header.Version);
+ Stream_Read_UINT8(s, header.MessageId);
+
+ switch (header.MessageId)
+ {
+ case CAM_MSG_ID_SelectVersionRequest:
+ error =
+ enumerator_server_handle_select_version_request(&enumerator->context, s, &header);
+ break;
+ case CAM_MSG_ID_DeviceAddedNotification:
+ error =
+ enumerator_server_recv_device_added_notification(&enumerator->context, s, &header);
+ break;
+ case CAM_MSG_ID_DeviceRemovedNotification:
+ error = enumerator_server_recv_device_removed_notification(&enumerator->context, s,
+ &header);
+ break;
+ default:
+ WLog_ERR(TAG, "enumerator_process_message: unknown or invalid MessageId %" PRIu8 "",
+ header.MessageId);
+ break;
+ }
+
+out:
+ if (error)
+ WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+static UINT enumerator_server_context_poll_int(CamDevEnumServerContext* context)
+{
+ enumerator_server* enumerator = (enumerator_server*)context;
+ UINT error = ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(enumerator);
+
+ switch (enumerator->state)
+ {
+ case ENUMERATOR_INITIAL:
+ error = enumerator_server_open_channel(enumerator);
+ if (error)
+ WLog_ERR(TAG, "enumerator_server_open_channel failed with error %" PRIu32 "!",
+ error);
+ else
+ enumerator->state = ENUMERATOR_OPENED;
+ break;
+ case ENUMERATOR_OPENED:
+ error = enumerator_process_message(enumerator);
+ break;
+ }
+
+ return error;
+}
+
+static HANDLE enumerator_server_get_channel_handle(enumerator_server* enumerator)
+{
+ void* buffer = NULL;
+ DWORD BytesReturned = 0;
+ HANDLE ChannelEvent = NULL;
+
+ WINPR_ASSERT(enumerator);
+
+ if (WTSVirtualChannelQuery(enumerator->enumerator_channel, WTSVirtualEventHandle, &buffer,
+ &BytesReturned) == TRUE)
+ {
+ if (BytesReturned == sizeof(HANDLE))
+ CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE));
+
+ WTSFreeMemory(buffer);
+ }
+
+ return ChannelEvent;
+}
+
+static DWORD WINAPI enumerator_server_thread_func(LPVOID arg)
+{
+ DWORD nCount = 0;
+ HANDLE events[2] = { 0 };
+ enumerator_server* enumerator = (enumerator_server*)arg;
+ UINT error = CHANNEL_RC_OK;
+ DWORD status = 0;
+
+ WINPR_ASSERT(enumerator);
+
+ nCount = 0;
+ events[nCount++] = enumerator->stopEvent;
+
+ while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0))
+ {
+ switch (enumerator->state)
+ {
+ case ENUMERATOR_INITIAL:
+ error = enumerator_server_context_poll_int(&enumerator->context);
+ if (error == CHANNEL_RC_OK)
+ {
+ events[1] = enumerator_server_get_channel_handle(enumerator);
+ nCount = 2;
+ }
+ break;
+ case ENUMERATOR_OPENED:
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+ switch (status)
+ {
+ case WAIT_OBJECT_0:
+ break;
+ case WAIT_OBJECT_0 + 1:
+ case WAIT_TIMEOUT:
+ error = enumerator_server_context_poll_int(&enumerator->context);
+ break;
+
+ case WAIT_FAILED:
+ default:
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+ break;
+ }
+ }
+
+ WTSVirtualChannelClose(enumerator->enumerator_channel);
+ enumerator->enumerator_channel = NULL;
+
+ if (error && enumerator->context.rdpcontext)
+ setChannelError(enumerator->context.rdpcontext, error,
+ "enumerator_server_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+static UINT enumerator_server_open(CamDevEnumServerContext* context)
+{
+ enumerator_server* enumerator = (enumerator_server*)context;
+
+ WINPR_ASSERT(enumerator);
+
+ if (!enumerator->externalThread && (enumerator->thread == NULL))
+ {
+ enumerator->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!enumerator->stopEvent)
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ enumerator->thread =
+ CreateThread(NULL, 0, enumerator_server_thread_func, enumerator, 0, NULL);
+ if (!enumerator->thread)
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ CloseHandle(enumerator->stopEvent);
+ enumerator->stopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+ enumerator->isOpened = TRUE;
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT enumerator_server_close(CamDevEnumServerContext* context)
+{
+ UINT error = CHANNEL_RC_OK;
+ enumerator_server* enumerator = (enumerator_server*)context;
+
+ WINPR_ASSERT(enumerator);
+
+ if (!enumerator->externalThread && enumerator->thread)
+ {
+ SetEvent(enumerator->stopEvent);
+
+ if (WaitForSingleObject(enumerator->thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ CloseHandle(enumerator->thread);
+ CloseHandle(enumerator->stopEvent);
+ enumerator->thread = NULL;
+ enumerator->stopEvent = NULL;
+ }
+ if (enumerator->externalThread)
+ {
+ if (enumerator->state != ENUMERATOR_INITIAL)
+ {
+ WTSVirtualChannelClose(enumerator->enumerator_channel);
+ enumerator->enumerator_channel = NULL;
+ enumerator->state = ENUMERATOR_INITIAL;
+ }
+ }
+ enumerator->isOpened = FALSE;
+
+ return error;
+}
+
+static UINT enumerator_server_context_poll(CamDevEnumServerContext* context)
+{
+ enumerator_server* enumerator = (enumerator_server*)context;
+
+ WINPR_ASSERT(enumerator);
+
+ if (!enumerator->externalThread)
+ return ERROR_INTERNAL_ERROR;
+
+ return enumerator_server_context_poll_int(context);
+}
+
+static BOOL enumerator_server_context_handle(CamDevEnumServerContext* context, HANDLE* handle)
+{
+ enumerator_server* enumerator = (enumerator_server*)context;
+
+ WINPR_ASSERT(enumerator);
+ WINPR_ASSERT(handle);
+
+ if (!enumerator->externalThread)
+ return FALSE;
+ if (enumerator->state == ENUMERATOR_INITIAL)
+ return FALSE;
+
+ *handle = enumerator_server_get_channel_handle(enumerator);
+
+ return TRUE;
+}
+
+static UINT enumerator_server_packet_send(CamDevEnumServerContext* context, wStream* s)
+{
+ enumerator_server* enumerator = (enumerator_server*)context;
+ UINT error = CHANNEL_RC_OK;
+ ULONG written = 0;
+
+ if (!WTSVirtualChannelWrite(enumerator->enumerator_channel, (PCHAR)Stream_Buffer(s),
+ Stream_GetPosition(s), &written))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ if (written < Stream_GetPosition(s))
+ {
+ WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written,
+ Stream_GetPosition(s));
+ }
+
+out:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+static UINT enumerator_send_select_version_response_pdu(
+ CamDevEnumServerContext* context, const CAM_SELECT_VERSION_RESPONSE* selectVersionResponse)
+{
+ wStream* s = NULL;
+
+ s = Stream_New(NULL, CAM_HEADER_SIZE);
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ Stream_Write_UINT8(s, selectVersionResponse->Header.Version);
+ Stream_Write_UINT8(s, selectVersionResponse->Header.MessageId);
+
+ return enumerator_server_packet_send(context, s);
+}
+
+CamDevEnumServerContext* cam_dev_enum_server_context_new(HANDLE vcm)
+{
+ enumerator_server* enumerator = (enumerator_server*)calloc(1, sizeof(enumerator_server));
+
+ if (!enumerator)
+ return NULL;
+
+ enumerator->context.vcm = vcm;
+ enumerator->context.Initialize = enumerator_server_initialize;
+ enumerator->context.Open = enumerator_server_open;
+ enumerator->context.Close = enumerator_server_close;
+ enumerator->context.Poll = enumerator_server_context_poll;
+ enumerator->context.ChannelHandle = enumerator_server_context_handle;
+
+ enumerator->context.SelectVersionResponse = enumerator_send_select_version_response_pdu;
+
+ enumerator->buffer = Stream_New(NULL, 4096);
+ if (!enumerator->buffer)
+ goto fail;
+
+ return &enumerator->context;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ cam_dev_enum_server_context_free(&enumerator->context);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void cam_dev_enum_server_context_free(CamDevEnumServerContext* context)
+{
+ enumerator_server* enumerator = (enumerator_server*)context;
+
+ if (enumerator)
+ {
+ enumerator_server_close(context);
+ Stream_Free(enumerator->buffer, TRUE);
+ }
+
+ free(enumerator);
+}
diff --git a/channels/rdpecam/server/camera_device_main.c b/channels/rdpecam/server/camera_device_main.c
new file mode 100644
index 0000000..ce0774c
--- /dev/null
+++ b/channels/rdpecam/server/camera_device_main.c
@@ -0,0 +1,966 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Capture Virtual Channel Extension
+ *
+ * Copyright 2022 Pascal Nowack <Pascal.Nowack@gmx.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/server/rdpecam.h>
+
+#define TAG CHANNELS_TAG("rdpecam.server")
+
+typedef enum
+{
+ CAMERA_DEVICE_INITIAL,
+ CAMERA_DEVICE_OPENED,
+} eCameraDeviceChannelState;
+
+typedef struct
+{
+ CameraDeviceServerContext context;
+
+ HANDLE stopEvent;
+
+ HANDLE thread;
+ void* device_channel;
+
+ DWORD SessionId;
+
+ BOOL isOpened;
+ BOOL externalThread;
+
+ /* Channel state */
+ eCameraDeviceChannelState state;
+
+ wStream* buffer;
+} device_server;
+
+static UINT device_server_initialize(CameraDeviceServerContext* context, BOOL externalThread)
+{
+ UINT error = CHANNEL_RC_OK;
+ device_server* device = (device_server*)context;
+
+ WINPR_ASSERT(device);
+
+ if (device->isOpened)
+ {
+ WLog_WARN(TAG, "Application error: Camera channel already initialized, "
+ "calling in this state is not possible!");
+ return ERROR_INVALID_STATE;
+ }
+
+ device->externalThread = externalThread;
+
+ return error;
+}
+
+static UINT device_server_open_channel(device_server* device)
+{
+ CameraDeviceServerContext* context = &device->context;
+ DWORD Error = ERROR_SUCCESS;
+ HANDLE hEvent = NULL;
+ DWORD BytesReturned = 0;
+ PULONG pSessionId = NULL;
+ UINT32 channelId = 0;
+ BOOL status = TRUE;
+
+ WINPR_ASSERT(device);
+
+ if (WTSQuerySessionInformationA(device->context.vcm, WTS_CURRENT_SESSION, WTSSessionId,
+ (LPSTR*)&pSessionId, &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSQuerySessionInformationA failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ device->SessionId = (DWORD)*pSessionId;
+ WTSFreeMemory(pSessionId);
+ hEvent = WTSVirtualChannelManagerGetEventHandle(device->context.vcm);
+
+ if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED)
+ {
+ Error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error);
+ return Error;
+ }
+
+ device->device_channel = WTSVirtualChannelOpenEx(device->SessionId, context->virtualChannelName,
+ WTS_CHANNEL_OPTION_DYNAMIC);
+ if (!device->device_channel)
+ {
+ Error = GetLastError();
+ WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", Error);
+ return Error;
+ }
+
+ channelId = WTSChannelGetIdByHandle(device->device_channel);
+
+ IFCALLRET(context->ChannelIdAssigned, status, context, channelId);
+ if (!status)
+ {
+ WLog_ERR(TAG, "context->ChannelIdAssigned failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return Error;
+}
+
+static UINT device_server_handle_success_response(CameraDeviceServerContext* context, wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_SUCCESS_RESPONSE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ IFCALLRET(context->SuccessResponse, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->SuccessResponse failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT device_server_recv_error_response(CameraDeviceServerContext* context, wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_ERROR_RESPONSE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT32(s, pdu.ErrorCode);
+
+ IFCALLRET(context->ErrorResponse, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->ErrorResponse failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT device_server_recv_stream_list_response(CameraDeviceServerContext* context, wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_STREAM_LIST_RESPONSE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 5))
+ return ERROR_NO_DATA;
+
+ pdu.N_Descriptions = MIN(Stream_GetRemainingLength(s) / 5, 255);
+
+ for (BYTE i = 0; i < pdu.N_Descriptions; ++i)
+ {
+ CAM_STREAM_DESCRIPTION* StreamDescription = &pdu.StreamDescriptions[i];
+
+ Stream_Read_UINT16(s, StreamDescription->FrameSourceTypes);
+ Stream_Read_UINT8(s, StreamDescription->StreamCategory);
+ Stream_Read_UINT8(s, StreamDescription->Selected);
+ Stream_Read_UINT8(s, StreamDescription->CanBeShared);
+ }
+
+ IFCALLRET(context->StreamListResponse, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->StreamListResponse failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT device_server_recv_media_type_list_response(CameraDeviceServerContext* context,
+ wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_MEDIA_TYPE_LIST_RESPONSE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 26))
+ return ERROR_NO_DATA;
+
+ pdu.N_Descriptions = Stream_GetRemainingLength(s) / 26;
+
+ pdu.MediaTypeDescriptions = calloc(pdu.N_Descriptions, sizeof(CAM_MEDIA_TYPE_DESCRIPTION));
+ if (!pdu.MediaTypeDescriptions)
+ {
+ WLog_ERR(TAG, "Failed to allocate %zu CAM_MEDIA_TYPE_DESCRIPTION structs",
+ pdu.N_Descriptions);
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ for (size_t i = 0; i < pdu.N_Descriptions; ++i)
+ {
+ CAM_MEDIA_TYPE_DESCRIPTION* MediaTypeDescriptions = &pdu.MediaTypeDescriptions[i];
+
+ Stream_Read_UINT8(s, MediaTypeDescriptions->Format);
+ Stream_Read_UINT32(s, MediaTypeDescriptions->Width);
+ Stream_Read_UINT32(s, MediaTypeDescriptions->Height);
+ Stream_Read_UINT32(s, MediaTypeDescriptions->FrameRateNumerator);
+ Stream_Read_UINT32(s, MediaTypeDescriptions->FrameRateDenominator);
+ Stream_Read_UINT32(s, MediaTypeDescriptions->PixelAspectRatioNumerator);
+ Stream_Read_UINT32(s, MediaTypeDescriptions->PixelAspectRatioDenominator);
+ Stream_Read_UINT8(s, MediaTypeDescriptions->Flags);
+ }
+
+ IFCALLRET(context->MediaTypeListResponse, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->MediaTypeListResponse failed with error %" PRIu32 "", error);
+
+ free(pdu.MediaTypeDescriptions);
+
+ return error;
+}
+
+static UINT device_server_recv_current_media_type_response(CameraDeviceServerContext* context,
+ wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_CURRENT_MEDIA_TYPE_RESPONSE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 26))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT8(s, pdu.MediaTypeDescription.Format);
+ Stream_Read_UINT32(s, pdu.MediaTypeDescription.Width);
+ Stream_Read_UINT32(s, pdu.MediaTypeDescription.Height);
+ Stream_Read_UINT32(s, pdu.MediaTypeDescription.FrameRateNumerator);
+ Stream_Read_UINT32(s, pdu.MediaTypeDescription.FrameRateDenominator);
+ Stream_Read_UINT32(s, pdu.MediaTypeDescription.PixelAspectRatioNumerator);
+ Stream_Read_UINT32(s, pdu.MediaTypeDescription.PixelAspectRatioDenominator);
+ Stream_Read_UINT8(s, pdu.MediaTypeDescription.Flags);
+
+ IFCALLRET(context->CurrentMediaTypeResponse, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->CurrentMediaTypeResponse failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT device_server_recv_sample_response(CameraDeviceServerContext* context, wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_SAMPLE_RESPONSE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT8(s, pdu.StreamIndex);
+
+ pdu.SampleSize = Stream_GetRemainingLength(s);
+ pdu.Sample = Stream_Pointer(s);
+
+ IFCALLRET(context->SampleResponse, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->SampleResponse failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT device_server_recv_sample_error_response(CameraDeviceServerContext* context, wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_SAMPLE_ERROR_RESPONSE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 5))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT8(s, pdu.StreamIndex);
+ Stream_Read_UINT32(s, pdu.ErrorCode);
+
+ IFCALLRET(context->SampleErrorResponse, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->SampleErrorResponse failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT device_server_recv_property_list_response(CameraDeviceServerContext* context,
+ wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_PROPERTY_LIST_RESPONSE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ pdu.N_Properties = Stream_GetRemainingLength(s) / 19;
+
+ if (pdu.N_Properties > 0)
+ {
+ pdu.Properties = calloc(pdu.N_Properties, sizeof(CAM_PROPERTY_DESCRIPTION));
+ if (!pdu.Properties)
+ {
+ WLog_ERR(TAG, "Failed to allocate %zu CAM_PROPERTY_DESCRIPTION structs",
+ pdu.N_Properties);
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ for (size_t i = 0; i < pdu.N_Properties; ++i)
+ {
+ Stream_Read_UINT8(s, pdu.Properties[i].PropertySet);
+ Stream_Read_UINT8(s, pdu.Properties[i].PropertyId);
+ Stream_Read_UINT8(s, pdu.Properties[i].Capabilities);
+ Stream_Read_INT32(s, pdu.Properties[i].MinValue);
+ Stream_Read_INT32(s, pdu.Properties[i].MaxValue);
+ Stream_Read_INT32(s, pdu.Properties[i].Step);
+ Stream_Read_INT32(s, pdu.Properties[i].DefaultValue);
+ }
+ }
+
+ IFCALLRET(context->PropertyListResponse, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->PropertyListResponse failed with error %" PRIu32 "", error);
+
+ free(pdu.Properties);
+
+ return error;
+}
+
+static UINT device_server_recv_property_value_response(CameraDeviceServerContext* context,
+ wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_PROPERTY_VALUE_RESPONSE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 5))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT8(s, pdu.PropertyValue.Mode);
+ Stream_Read_INT32(s, pdu.PropertyValue.Value);
+
+ IFCALLRET(context->PropertyValueResponse, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->PropertyValueResponse failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT device_process_message(device_server* device)
+{
+ BOOL rc = 0;
+ UINT error = ERROR_INTERNAL_ERROR;
+ ULONG BytesReturned = 0;
+ CAM_SHARED_MSG_HEADER header = { 0 };
+ wStream* s = NULL;
+
+ WINPR_ASSERT(device);
+ WINPR_ASSERT(device->device_channel);
+
+ s = device->buffer;
+ WINPR_ASSERT(s);
+
+ Stream_SetPosition(s, 0);
+ rc = WTSVirtualChannelRead(device->device_channel, 0, NULL, 0, &BytesReturned);
+ if (!rc)
+ goto out;
+
+ if (BytesReturned < 1)
+ {
+ error = CHANNEL_RC_OK;
+ goto out;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ if (WTSVirtualChannelRead(device->device_channel, 0, (PCHAR)Stream_Buffer(s),
+ (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ goto out;
+ }
+
+ Stream_SetLength(s, BytesReturned);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, CAM_HEADER_SIZE))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT8(s, header.Version);
+ Stream_Read_UINT8(s, header.MessageId);
+
+ switch (header.MessageId)
+ {
+ case CAM_MSG_ID_SuccessResponse:
+ error = device_server_handle_success_response(&device->context, s, &header);
+ break;
+ case CAM_MSG_ID_ErrorResponse:
+ error = device_server_recv_error_response(&device->context, s, &header);
+ break;
+ case CAM_MSG_ID_StreamListResponse:
+ error = device_server_recv_stream_list_response(&device->context, s, &header);
+ break;
+ case CAM_MSG_ID_MediaTypeListResponse:
+ error = device_server_recv_media_type_list_response(&device->context, s, &header);
+ break;
+ case CAM_MSG_ID_CurrentMediaTypeResponse:
+ error = device_server_recv_current_media_type_response(&device->context, s, &header);
+ break;
+ case CAM_MSG_ID_SampleResponse:
+ error = device_server_recv_sample_response(&device->context, s, &header);
+ break;
+ case CAM_MSG_ID_SampleErrorResponse:
+ error = device_server_recv_sample_error_response(&device->context, s, &header);
+ break;
+ case CAM_MSG_ID_PropertyListResponse:
+ error = device_server_recv_property_list_response(&device->context, s, &header);
+ break;
+ case CAM_MSG_ID_PropertyValueResponse:
+ error = device_server_recv_property_value_response(&device->context, s, &header);
+ break;
+ default:
+ WLog_ERR(TAG, "device_process_message: unknown or invalid MessageId %" PRIu8 "",
+ header.MessageId);
+ break;
+ }
+
+out:
+ if (error)
+ WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+static UINT device_server_context_poll_int(CameraDeviceServerContext* context)
+{
+ device_server* device = (device_server*)context;
+ UINT error = ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(device);
+
+ switch (device->state)
+ {
+ case CAMERA_DEVICE_INITIAL:
+ error = device_server_open_channel(device);
+ if (error)
+ WLog_ERR(TAG, "device_server_open_channel failed with error %" PRIu32 "!", error);
+ else
+ device->state = CAMERA_DEVICE_OPENED;
+ break;
+ case CAMERA_DEVICE_OPENED:
+ error = device_process_message(device);
+ break;
+ }
+
+ return error;
+}
+
+static HANDLE device_server_get_channel_handle(device_server* device)
+{
+ void* buffer = NULL;
+ DWORD BytesReturned = 0;
+ HANDLE ChannelEvent = NULL;
+
+ WINPR_ASSERT(device);
+
+ if (WTSVirtualChannelQuery(device->device_channel, WTSVirtualEventHandle, &buffer,
+ &BytesReturned) == TRUE)
+ {
+ if (BytesReturned == sizeof(HANDLE))
+ CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE));
+
+ WTSFreeMemory(buffer);
+ }
+
+ return ChannelEvent;
+}
+
+static DWORD WINAPI device_server_thread_func(LPVOID arg)
+{
+ DWORD nCount = 0;
+ HANDLE events[2] = { 0 };
+ device_server* device = (device_server*)arg;
+ UINT error = CHANNEL_RC_OK;
+ DWORD status = 0;
+
+ WINPR_ASSERT(device);
+
+ nCount = 0;
+ events[nCount++] = device->stopEvent;
+
+ while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0))
+ {
+ switch (device->state)
+ {
+ case CAMERA_DEVICE_INITIAL:
+ error = device_server_context_poll_int(&device->context);
+ if (error == CHANNEL_RC_OK)
+ {
+ events[1] = device_server_get_channel_handle(device);
+ nCount = 2;
+ }
+ break;
+ case CAMERA_DEVICE_OPENED:
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+ switch (status)
+ {
+ case WAIT_OBJECT_0:
+ break;
+ case WAIT_OBJECT_0 + 1:
+ case WAIT_TIMEOUT:
+ error = device_server_context_poll_int(&device->context);
+ break;
+
+ case WAIT_FAILED:
+ default:
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+ break;
+ }
+ }
+
+ WTSVirtualChannelClose(device->device_channel);
+ device->device_channel = NULL;
+
+ if (error && device->context.rdpcontext)
+ setChannelError(device->context.rdpcontext, error,
+ "device_server_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+static UINT device_server_open(CameraDeviceServerContext* context)
+{
+ device_server* device = (device_server*)context;
+
+ WINPR_ASSERT(device);
+
+ if (!device->externalThread && (device->thread == NULL))
+ {
+ device->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!device->stopEvent)
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ device->thread = CreateThread(NULL, 0, device_server_thread_func, device, 0, NULL);
+ if (!device->thread)
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ CloseHandle(device->stopEvent);
+ device->stopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+ device->isOpened = TRUE;
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT device_server_close(CameraDeviceServerContext* context)
+{
+ UINT error = CHANNEL_RC_OK;
+ device_server* device = (device_server*)context;
+
+ WINPR_ASSERT(device);
+
+ if (!device->externalThread && device->thread)
+ {
+ SetEvent(device->stopEvent);
+
+ if (WaitForSingleObject(device->thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ CloseHandle(device->thread);
+ CloseHandle(device->stopEvent);
+ device->thread = NULL;
+ device->stopEvent = NULL;
+ }
+ if (device->externalThread)
+ {
+ if (device->state != CAMERA_DEVICE_INITIAL)
+ {
+ WTSVirtualChannelClose(device->device_channel);
+ device->device_channel = NULL;
+ device->state = CAMERA_DEVICE_INITIAL;
+ }
+ }
+ device->isOpened = FALSE;
+
+ return error;
+}
+
+static UINT device_server_context_poll(CameraDeviceServerContext* context)
+{
+ device_server* device = (device_server*)context;
+
+ WINPR_ASSERT(device);
+
+ if (!device->externalThread)
+ return ERROR_INTERNAL_ERROR;
+
+ return device_server_context_poll_int(context);
+}
+
+static BOOL device_server_context_handle(CameraDeviceServerContext* context, HANDLE* handle)
+{
+ device_server* device = (device_server*)context;
+
+ WINPR_ASSERT(device);
+ WINPR_ASSERT(handle);
+
+ if (!device->externalThread)
+ return FALSE;
+ if (device->state == CAMERA_DEVICE_INITIAL)
+ return FALSE;
+
+ *handle = device_server_get_channel_handle(device);
+
+ return TRUE;
+}
+
+static wStream* device_server_packet_new(size_t size, BYTE version, BYTE messageId)
+{
+ wStream* s = NULL;
+
+ /* Allocate what we need plus header bytes */
+ s = Stream_New(NULL, size + CAM_HEADER_SIZE);
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return NULL;
+ }
+
+ Stream_Write_UINT8(s, version);
+ Stream_Write_UINT8(s, messageId);
+
+ return s;
+}
+
+static UINT device_server_packet_send(CameraDeviceServerContext* context, wStream* s)
+{
+ device_server* device = (device_server*)context;
+ UINT error = CHANNEL_RC_OK;
+ ULONG written = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(s);
+
+ if (!WTSVirtualChannelWrite(device->device_channel, (PCHAR)Stream_Buffer(s),
+ Stream_GetPosition(s), &written))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ if (written < Stream_GetPosition(s))
+ {
+ WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written,
+ Stream_GetPosition(s));
+ }
+
+out:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+static UINT device_server_write_and_send_header(CameraDeviceServerContext* context, BYTE messageId)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+
+ s = device_server_packet_new(0, context->protocolVersion, messageId);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ return device_server_packet_send(context, s);
+}
+
+static UINT
+device_send_activate_device_request_pdu(CameraDeviceServerContext* context,
+ const CAM_ACTIVATE_DEVICE_REQUEST* activateDeviceRequest)
+{
+ WINPR_ASSERT(context);
+
+ return device_server_write_and_send_header(context, CAM_MSG_ID_ActivateDeviceRequest);
+}
+
+static UINT device_send_deactivate_device_request_pdu(
+ CameraDeviceServerContext* context,
+ const CAM_DEACTIVATE_DEVICE_REQUEST* deactivateDeviceRequest)
+{
+ WINPR_ASSERT(context);
+
+ return device_server_write_and_send_header(context, CAM_MSG_ID_DeactivateDeviceRequest);
+}
+
+static UINT device_send_stream_list_request_pdu(CameraDeviceServerContext* context,
+ const CAM_STREAM_LIST_REQUEST* streamListRequest)
+{
+ WINPR_ASSERT(context);
+
+ return device_server_write_and_send_header(context, CAM_MSG_ID_StreamListRequest);
+}
+
+static UINT
+device_send_media_type_list_request_pdu(CameraDeviceServerContext* context,
+ const CAM_MEDIA_TYPE_LIST_REQUEST* mediaTypeListRequest)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(mediaTypeListRequest);
+
+ s = device_server_packet_new(1, context->protocolVersion, CAM_MSG_ID_MediaTypeListRequest);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT8(s, mediaTypeListRequest->StreamIndex);
+
+ return device_server_packet_send(context, s);
+}
+
+static UINT device_send_current_media_type_request_pdu(
+ CameraDeviceServerContext* context,
+ const CAM_CURRENT_MEDIA_TYPE_REQUEST* currentMediaTypeRequest)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(currentMediaTypeRequest);
+
+ s = device_server_packet_new(1, context->protocolVersion, CAM_MSG_ID_CurrentMediaTypeRequest);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT8(s, currentMediaTypeRequest->StreamIndex);
+
+ return device_server_packet_send(context, s);
+}
+
+static UINT
+device_send_start_streams_request_pdu(CameraDeviceServerContext* context,
+ const CAM_START_STREAMS_REQUEST* startStreamsRequest)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(startStreamsRequest);
+
+ s = device_server_packet_new(startStreamsRequest->N_Infos * 27ul, context->protocolVersion,
+ CAM_MSG_ID_StartStreamsRequest);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ for (size_t i = 0; i < startStreamsRequest->N_Infos; ++i)
+ {
+ const CAM_START_STREAM_INFO* info = &startStreamsRequest->StartStreamsInfo[i];
+ const CAM_MEDIA_TYPE_DESCRIPTION* description = &info->MediaTypeDescription;
+
+ Stream_Write_UINT8(s, info->StreamIndex);
+
+ Stream_Write_UINT8(s, description->Format);
+ Stream_Write_UINT32(s, description->Width);
+ Stream_Write_UINT32(s, description->Height);
+ Stream_Write_UINT32(s, description->FrameRateNumerator);
+ Stream_Write_UINT32(s, description->FrameRateDenominator);
+ Stream_Write_UINT32(s, description->PixelAspectRatioNumerator);
+ Stream_Write_UINT32(s, description->PixelAspectRatioDenominator);
+ Stream_Write_UINT8(s, description->Flags);
+ }
+
+ return device_server_packet_send(context, s);
+}
+
+static UINT device_send_stop_streams_request_pdu(CameraDeviceServerContext* context,
+ const CAM_STOP_STREAMS_REQUEST* stopStreamsRequest)
+{
+ WINPR_ASSERT(context);
+
+ return device_server_write_and_send_header(context, CAM_MSG_ID_StopStreamsRequest);
+}
+
+static UINT device_send_sample_request_pdu(CameraDeviceServerContext* context,
+ const CAM_SAMPLE_REQUEST* sampleRequest)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(sampleRequest);
+
+ s = device_server_packet_new(1, context->protocolVersion, CAM_MSG_ID_SampleRequest);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT8(s, sampleRequest->StreamIndex);
+
+ return device_server_packet_send(context, s);
+}
+
+static UINT
+device_send_property_list_request_pdu(CameraDeviceServerContext* context,
+ const CAM_PROPERTY_LIST_REQUEST* propertyListRequest)
+{
+ WINPR_ASSERT(context);
+
+ return device_server_write_and_send_header(context, CAM_MSG_ID_PropertyListRequest);
+}
+
+static UINT
+device_send_property_value_request_pdu(CameraDeviceServerContext* context,
+ const CAM_PROPERTY_VALUE_REQUEST* propertyValueRequest)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(propertyValueRequest);
+
+ s = device_server_packet_new(2, context->protocolVersion, CAM_MSG_ID_PropertyValueRequest);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT8(s, propertyValueRequest->PropertySet);
+ Stream_Write_UINT8(s, propertyValueRequest->PropertyId);
+
+ return device_server_packet_send(context, s);
+}
+
+static UINT device_send_set_property_value_request_pdu(
+ CameraDeviceServerContext* context,
+ const CAM_SET_PROPERTY_VALUE_REQUEST* setPropertyValueRequest)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(setPropertyValueRequest);
+
+ s = device_server_packet_new(2 + 5, context->protocolVersion,
+ CAM_MSG_ID_SetPropertyValueRequest);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT8(s, setPropertyValueRequest->PropertySet);
+ Stream_Write_UINT8(s, setPropertyValueRequest->PropertyId);
+
+ Stream_Write_UINT8(s, setPropertyValueRequest->PropertyValue.Mode);
+ Stream_Write_INT32(s, setPropertyValueRequest->PropertyValue.Value);
+
+ return device_server_packet_send(context, s);
+}
+
+CameraDeviceServerContext* camera_device_server_context_new(HANDLE vcm)
+{
+ device_server* device = (device_server*)calloc(1, sizeof(device_server));
+
+ if (!device)
+ return NULL;
+
+ device->context.vcm = vcm;
+ device->context.Initialize = device_server_initialize;
+ device->context.Open = device_server_open;
+ device->context.Close = device_server_close;
+ device->context.Poll = device_server_context_poll;
+ device->context.ChannelHandle = device_server_context_handle;
+
+ device->context.ActivateDeviceRequest = device_send_activate_device_request_pdu;
+ device->context.DeactivateDeviceRequest = device_send_deactivate_device_request_pdu;
+
+ device->context.StreamListRequest = device_send_stream_list_request_pdu;
+ device->context.MediaTypeListRequest = device_send_media_type_list_request_pdu;
+ device->context.CurrentMediaTypeRequest = device_send_current_media_type_request_pdu;
+
+ device->context.StartStreamsRequest = device_send_start_streams_request_pdu;
+ device->context.StopStreamsRequest = device_send_stop_streams_request_pdu;
+ device->context.SampleRequest = device_send_sample_request_pdu;
+
+ device->context.PropertyListRequest = device_send_property_list_request_pdu;
+ device->context.PropertyValueRequest = device_send_property_value_request_pdu;
+ device->context.SetPropertyValueRequest = device_send_set_property_value_request_pdu;
+
+ device->buffer = Stream_New(NULL, 4096);
+ if (!device->buffer)
+ goto fail;
+
+ return &device->context;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ camera_device_server_context_free(&device->context);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void camera_device_server_context_free(CameraDeviceServerContext* context)
+{
+ device_server* device = (device_server*)context;
+
+ if (device)
+ {
+ device_server_close(context);
+ Stream_Free(device->buffer, TRUE);
+ }
+
+ free(context->virtualChannelName);
+
+ free(device);
+}
diff --git a/channels/rdpei/CMakeLists.txt b/channels/rdpei/CMakeLists.txt
new file mode 100644
index 0000000..a93af67
--- /dev/null
+++ b/channels/rdpei/CMakeLists.txt
@@ -0,0 +1,26 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("rdpei")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif() \ No newline at end of file
diff --git a/channels/rdpei/ChannelOptions.cmake b/channels/rdpei/ChannelOptions.cmake
new file mode 100644
index 0000000..d3f8743
--- /dev/null
+++ b/channels/rdpei/ChannelOptions.cmake
@@ -0,0 +1,13 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT OFF)
+
+define_channel_options(NAME "rdpei" TYPE "dynamic"
+ DESCRIPTION "Input Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPEI]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/rdpei/client/CMakeLists.txt b/channels/rdpei/client/CMakeLists.txt
new file mode 100644
index 0000000..8ef4712
--- /dev/null
+++ b/channels/rdpei/client/CMakeLists.txt
@@ -0,0 +1,32 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("rdpei")
+
+set(${MODULE_PREFIX}_SRCS
+ rdpei_main.c
+ rdpei_main.h
+ ../rdpei_common.c
+ ../rdpei_common.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr freerdp
+)
+include_directories(..)
+
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
diff --git a/channels/rdpei/client/rdpei_main.c b/channels/rdpei/client/rdpei_main.c
new file mode 100644
index 0000000..fbb255d
--- /dev/null
+++ b/channels/rdpei/client/rdpei_main.c
@@ -0,0 +1,1467 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Input Virtual Channel Extension
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/sysinfo.h>
+#include <winpr/cmdline.h>
+#include <winpr/collections.h>
+
+#include <freerdp/addin.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/client/channels.h>
+
+#include "rdpei_common.h"
+
+#include "rdpei_main.h"
+
+/**
+ * Touch Input
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/dd562197/
+ *
+ * Windows Touch Input
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/dd317321/
+ *
+ * Input: Touch injection sample
+ * http://code.msdn.microsoft.com/windowsdesktop/Touch-Injection-Sample-444d9bf7
+ *
+ * Pointer Input Message Reference
+ * http://msdn.microsoft.com/en-us/library/hh454916/
+ *
+ * POINTER_INFO Structure
+ * http://msdn.microsoft.com/en-us/library/hh454907/
+ *
+ * POINTER_TOUCH_INFO Structure
+ * http://msdn.microsoft.com/en-us/library/hh454910/
+ */
+
+#define MAX_CONTACTS 64
+#define MAX_PEN_CONTACTS 4
+
+typedef struct
+{
+ GENERIC_DYNVC_PLUGIN base;
+
+ RdpeiClientContext* context;
+
+ UINT32 version;
+ UINT32 features; /* SC_READY_MULTIPEN_INJECTION_SUPPORTED */
+ UINT16 maxTouchContacts;
+ UINT64 currentFrameTime;
+ UINT64 previousFrameTime;
+ RDPINPUT_CONTACT_POINT contactPoints[MAX_CONTACTS];
+
+ UINT64 currentPenFrameTime;
+ UINT64 previousPenFrameTime;
+ UINT16 maxPenContacts;
+ RDPINPUT_PEN_CONTACT_POINT penContactPoints[MAX_PEN_CONTACTS];
+
+ CRITICAL_SECTION lock;
+ rdpContext* rdpcontext;
+ HANDLE thread;
+ HANDLE event;
+ BOOL running;
+} RDPEI_PLUGIN;
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_send_frame(RdpeiClientContext* context, RDPINPUT_TOUCH_FRAME* frame);
+
+#ifdef WITH_DEBUG_RDPEI
+static const char* rdpei_eventid_string(UINT16 event)
+{
+ switch (event)
+ {
+ case EVENTID_SC_READY:
+ return "EVENTID_SC_READY";
+ case EVENTID_CS_READY:
+ return "EVENTID_CS_READY";
+ case EVENTID_TOUCH:
+ return "EVENTID_TOUCH";
+ case EVENTID_SUSPEND_TOUCH:
+ return "EVENTID_SUSPEND_TOUCH";
+ case EVENTID_RESUME_TOUCH:
+ return "EVENTID_RESUME_TOUCH";
+ case EVENTID_DISMISS_HOVERING_CONTACT:
+ return "EVENTID_DISMISS_HOVERING_CONTACT";
+ case EVENTID_PEN:
+ return "EVENTID_PEN";
+ default:
+ return "EVENTID_UNKNOWN";
+ }
+}
+#endif
+
+static RDPINPUT_CONTACT_POINT* rdpei_contact(RDPEI_PLUGIN* rdpei, INT32 externalId, BOOL active)
+{
+ for (UINT16 i = 0; i < rdpei->maxTouchContacts; i++)
+ {
+ RDPINPUT_CONTACT_POINT* contactPoint = &rdpei->contactPoints[i];
+
+ if (!contactPoint->active && active)
+ continue;
+ else if (!contactPoint->active && !active)
+ {
+ contactPoint->contactId = i;
+ contactPoint->externalId = externalId;
+ contactPoint->active = TRUE;
+ return contactPoint;
+ }
+ else if (contactPoint->externalId == externalId)
+ {
+ return contactPoint;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_add_frame(RdpeiClientContext* context)
+{
+ RDPEI_PLUGIN* rdpei = NULL;
+ RDPINPUT_TOUCH_FRAME frame = { 0 };
+ RDPINPUT_CONTACT_DATA contacts[MAX_CONTACTS] = { 0 };
+
+ if (!context || !context->handle)
+ return ERROR_INTERNAL_ERROR;
+
+ rdpei = (RDPEI_PLUGIN*)context->handle;
+ frame.contacts = contacts;
+
+ for (UINT16 i = 0; i < rdpei->maxTouchContacts; i++)
+ {
+ RDPINPUT_CONTACT_POINT* contactPoint = &rdpei->contactPoints[i];
+ RDPINPUT_CONTACT_DATA* contact = &contactPoint->data;
+
+ if (contactPoint->dirty)
+ {
+ contacts[frame.contactCount] = *contact;
+ rdpei->contactPoints[i].dirty = FALSE;
+ frame.contactCount++;
+ }
+ else if (contactPoint->active)
+ {
+ if (contact->contactFlags & RDPINPUT_CONTACT_FLAG_DOWN)
+ {
+ contact->contactFlags = RDPINPUT_CONTACT_FLAG_UPDATE;
+ contact->contactFlags |= RDPINPUT_CONTACT_FLAG_INRANGE;
+ contact->contactFlags |= RDPINPUT_CONTACT_FLAG_INCONTACT;
+ }
+
+ contacts[frame.contactCount] = *contact;
+ frame.contactCount++;
+ }
+ if (contact->contactFlags & RDPINPUT_CONTACT_FLAG_UP)
+ {
+ contactPoint->active = FALSE;
+ contactPoint->externalId = 0;
+ contactPoint->contactId = 0;
+ }
+ }
+
+ if (frame.contactCount > 0)
+ {
+ UINT error = rdpei_send_frame(context, &frame);
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "rdpei_send_frame failed with error %" PRIu32 "!", error);
+ return error;
+ }
+ }
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_send_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s, UINT16 eventId,
+ UINT32 pduLength)
+{
+ UINT status = 0;
+
+ if (!callback || !s || !callback->channel || !callback->channel->Write || !callback->plugin)
+ return ERROR_INTERNAL_ERROR;
+
+ Stream_SetPosition(s, 0);
+ Stream_Write_UINT16(s, eventId); /* eventId (2 bytes) */
+ Stream_Write_UINT32(s, pduLength); /* pduLength (4 bytes) */
+ Stream_SetPosition(s, Stream_Length(s));
+ status = callback->channel->Write(callback->channel, (UINT32)Stream_Length(s), Stream_Buffer(s),
+ NULL);
+#ifdef WITH_DEBUG_RDPEI
+ WLog_DBG(TAG,
+ "rdpei_send_pdu: eventId: %" PRIu16 " (%s) length: %" PRIu32 " status: %" PRIu32 "",
+ eventId, rdpei_eventid_string(eventId), pduLength, status);
+#endif
+ return status;
+}
+
+static UINT rdpei_write_pen_frame(wStream* s, const RDPINPUT_PEN_FRAME* frame)
+{
+ if (!s || !frame)
+ return ERROR_INTERNAL_ERROR;
+
+ if (!rdpei_write_2byte_unsigned(s, frame->contactCount))
+ return ERROR_OUTOFMEMORY;
+ if (!rdpei_write_8byte_unsigned(s, frame->frameOffset))
+ return ERROR_OUTOFMEMORY;
+ for (UINT16 x = 0; x < frame->contactCount; x++)
+ {
+ const RDPINPUT_PEN_CONTACT* contact = &frame->contacts[x];
+
+ if (!Stream_EnsureRemainingCapacity(s, 1))
+ return ERROR_OUTOFMEMORY;
+ Stream_Write_UINT8(s, contact->deviceId);
+ if (!rdpei_write_2byte_unsigned(s, contact->fieldsPresent))
+ return ERROR_OUTOFMEMORY;
+ if (!rdpei_write_4byte_signed(s, contact->x))
+ return ERROR_OUTOFMEMORY;
+ if (!rdpei_write_4byte_signed(s, contact->y))
+ return ERROR_OUTOFMEMORY;
+ if (!rdpei_write_4byte_unsigned(s, contact->contactFlags))
+ return ERROR_OUTOFMEMORY;
+ if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT)
+ {
+ if (!rdpei_write_4byte_unsigned(s, contact->penFlags))
+ return ERROR_OUTOFMEMORY;
+ }
+ if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT)
+ {
+ if (!rdpei_write_4byte_unsigned(s, contact->pressure))
+ return ERROR_OUTOFMEMORY;
+ }
+ if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_ROTATION_PRESENT)
+ {
+ if (!rdpei_write_2byte_unsigned(s, contact->rotation))
+ return ERROR_OUTOFMEMORY;
+ }
+ if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTX_PRESENT)
+ {
+ if (!rdpei_write_2byte_signed(s, contact->tiltX))
+ return ERROR_OUTOFMEMORY;
+ }
+ if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTY_PRESENT)
+ {
+ if (!rdpei_write_2byte_signed(s, contact->tiltY))
+ return ERROR_OUTOFMEMORY;
+ }
+ }
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpei_send_pen_event_pdu(GENERIC_CHANNEL_CALLBACK* callback, UINT32 frameOffset,
+ const RDPINPUT_PEN_FRAME* frames, UINT16 count)
+{
+ UINT status = 0;
+ wStream* s = NULL;
+
+ if (!frames || (count == 0))
+ return ERROR_INTERNAL_ERROR;
+
+ s = Stream_New(NULL, 64);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Seek(s, RDPINPUT_HEADER_LENGTH);
+ /**
+ * the time that has elapsed (in milliseconds) from when the oldest touch frame
+ * was generated to when it was encoded for transmission by the client.
+ */
+ rdpei_write_4byte_unsigned(s, frameOffset); /* encodeTime (FOUR_BYTE_UNSIGNED_INTEGER) */
+ rdpei_write_2byte_unsigned(s, count); /* (frameCount) TWO_BYTE_UNSIGNED_INTEGER */
+
+ for (UINT16 x = 0; x < count; x++)
+ {
+ if ((status = rdpei_write_pen_frame(s, &frames[x])))
+ {
+ WLog_ERR(TAG, "rdpei_write_pen_frame failed with error %" PRIu32 "!", status);
+ Stream_Free(s, TRUE);
+ return status;
+ }
+ }
+ Stream_SealLength(s);
+
+ status = rdpei_send_pdu(callback, s, EVENTID_PEN, Stream_Length(s));
+ Stream_Free(s, TRUE);
+ return status;
+}
+
+static UINT rdpei_send_pen_frame(RdpeiClientContext* context, RDPINPUT_PEN_FRAME* frame)
+{
+ const UINT64 currentTime = GetTickCount64();
+ RDPEI_PLUGIN* rdpei = NULL;
+ GENERIC_CHANNEL_CALLBACK* callback = NULL;
+ UINT error = 0;
+
+ if (!context)
+ return ERROR_INTERNAL_ERROR;
+ rdpei = (RDPEI_PLUGIN*)context->handle;
+ if (!rdpei || !rdpei->base.listener_callback)
+ return ERROR_INTERNAL_ERROR;
+ if (!rdpei || !rdpei->rdpcontext)
+ return ERROR_INTERNAL_ERROR;
+ if (freerdp_settings_get_bool(rdpei->rdpcontext->settings, FreeRDP_SuspendInput))
+ return CHANNEL_RC_OK;
+
+ callback = rdpei->base.listener_callback->channel_callback;
+ /* Just ignore the event if the channel is not connected */
+ if (!callback)
+ return CHANNEL_RC_OK;
+
+ if (!rdpei->previousPenFrameTime && !rdpei->currentPenFrameTime)
+ {
+ rdpei->currentPenFrameTime = currentTime;
+ frame->frameOffset = 0;
+ }
+ else
+ {
+ rdpei->currentPenFrameTime = currentTime;
+ frame->frameOffset = rdpei->currentPenFrameTime - rdpei->previousPenFrameTime;
+ }
+
+ if ((error = rdpei_send_pen_event_pdu(callback, frame->frameOffset, frame, 1)))
+ return error;
+
+ rdpei->previousPenFrameTime = rdpei->currentPenFrameTime;
+ return error;
+}
+
+static UINT rdpei_add_pen_frame(RdpeiClientContext* context)
+{
+ RDPEI_PLUGIN* rdpei = NULL;
+ RDPINPUT_PEN_FRAME penFrame = { 0 };
+ RDPINPUT_PEN_CONTACT penContacts[MAX_PEN_CONTACTS] = { 0 };
+
+ if (!context || !context->handle)
+ return ERROR_INTERNAL_ERROR;
+
+ rdpei = (RDPEI_PLUGIN*)context->handle;
+
+ penFrame.contacts = penContacts;
+
+ for (UINT16 i = 0; i < rdpei->maxPenContacts; i++)
+ {
+ RDPINPUT_PEN_CONTACT_POINT* contact = &(rdpei->penContactPoints[i]);
+
+ if (contact->dirty)
+ {
+ penContacts[penFrame.contactCount++] = contact->data;
+ contact->dirty = FALSE;
+ }
+ else if (contact->active)
+ {
+ if (contact->data.contactFlags & RDPINPUT_CONTACT_FLAG_DOWN)
+ {
+ contact->data.contactFlags = RDPINPUT_CONTACT_FLAG_UPDATE;
+ contact->data.contactFlags |= RDPINPUT_CONTACT_FLAG_INRANGE;
+ contact->data.contactFlags |= RDPINPUT_CONTACT_FLAG_INCONTACT;
+ }
+
+ penContacts[penFrame.contactCount++] = contact->data;
+ }
+ if (contact->data.contactFlags & RDPINPUT_CONTACT_FLAG_CANCELED)
+ {
+ contact->externalId = 0;
+ contact->active = FALSE;
+ }
+ }
+
+ if (penFrame.contactCount > 0)
+ return rdpei_send_pen_frame(context, &penFrame);
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpei_update(RdpeiClientContext* context)
+{
+ UINT error = rdpei_add_frame(context);
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "rdpei_add_frame failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ return rdpei_add_pen_frame(context);
+}
+
+static DWORD WINAPI rdpei_periodic_update(LPVOID arg)
+{
+ DWORD status = 0;
+ RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)arg;
+ UINT error = CHANNEL_RC_OK;
+ RdpeiClientContext* context = NULL;
+
+ if (!rdpei)
+ {
+ error = ERROR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ context = rdpei->context;
+
+ if (!context)
+ {
+ error = ERROR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ while (rdpei->running)
+ {
+ status = WaitForSingleObject(rdpei->event, 20);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error);
+ break;
+ }
+
+ EnterCriticalSection(&rdpei->lock);
+
+ error = rdpei_update(context);
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "rdpei_add_frame failed with error %" PRIu32 "!", error);
+ break;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ ResetEvent(rdpei->event);
+
+ LeaveCriticalSection(&rdpei->lock);
+ }
+
+out:
+
+ if (error && rdpei && rdpei->rdpcontext)
+ setChannelError(rdpei->rdpcontext, error, "rdpei_schedule_thread reported an error");
+
+ if (rdpei)
+ rdpei->running = FALSE;
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_send_cs_ready_pdu(GENERIC_CHANNEL_CALLBACK* callback)
+{
+ UINT status = 0;
+ wStream* s = NULL;
+ UINT32 flags = 0;
+ UINT32 pduLength = 0;
+ RDPEI_PLUGIN* rdpei = NULL;
+
+ if (!callback || !callback->plugin)
+ return ERROR_INTERNAL_ERROR;
+ rdpei = (RDPEI_PLUGIN*)callback->plugin;
+
+ flags |= CS_READY_FLAGS_SHOW_TOUCH_VISUALS & rdpei->context->clientFeaturesMask;
+ if (rdpei->version > RDPINPUT_PROTOCOL_V10)
+ flags |= CS_READY_FLAGS_DISABLE_TIMESTAMP_INJECTION & rdpei->context->clientFeaturesMask;
+ if (rdpei->features & SC_READY_MULTIPEN_INJECTION_SUPPORTED)
+ flags |= CS_READY_FLAGS_ENABLE_MULTIPEN_INJECTION & rdpei->context->clientFeaturesMask;
+
+ pduLength = RDPINPUT_HEADER_LENGTH + 10;
+ s = Stream_New(NULL, pduLength);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Seek(s, RDPINPUT_HEADER_LENGTH);
+ Stream_Write_UINT32(s, flags); /* flags (4 bytes) */
+ Stream_Write_UINT32(s, rdpei->version); /* protocolVersion (4 bytes) */
+ Stream_Write_UINT16(s, rdpei->maxTouchContacts); /* maxTouchContacts (2 bytes) */
+ Stream_SealLength(s);
+ status = rdpei_send_pdu(callback, s, EVENTID_CS_READY, pduLength);
+ Stream_Free(s, TRUE);
+ return status;
+}
+
+static void rdpei_print_contact_flags(UINT32 contactFlags)
+{
+ if (contactFlags & RDPINPUT_CONTACT_FLAG_DOWN)
+ WLog_DBG(TAG, " RDPINPUT_CONTACT_FLAG_DOWN");
+
+ if (contactFlags & RDPINPUT_CONTACT_FLAG_UPDATE)
+ WLog_DBG(TAG, " RDPINPUT_CONTACT_FLAG_UPDATE");
+
+ if (contactFlags & RDPINPUT_CONTACT_FLAG_UP)
+ WLog_DBG(TAG, " RDPINPUT_CONTACT_FLAG_UP");
+
+ if (contactFlags & RDPINPUT_CONTACT_FLAG_INRANGE)
+ WLog_DBG(TAG, " RDPINPUT_CONTACT_FLAG_INRANGE");
+
+ if (contactFlags & RDPINPUT_CONTACT_FLAG_INCONTACT)
+ WLog_DBG(TAG, " RDPINPUT_CONTACT_FLAG_INCONTACT");
+
+ if (contactFlags & RDPINPUT_CONTACT_FLAG_CANCELED)
+ WLog_DBG(TAG, " RDPINPUT_CONTACT_FLAG_CANCELED");
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_write_touch_frame(wStream* s, RDPINPUT_TOUCH_FRAME* frame)
+{
+ int rectSize = 2;
+ RDPINPUT_CONTACT_DATA* contact = NULL;
+ if (!s || !frame)
+ return ERROR_INTERNAL_ERROR;
+#ifdef WITH_DEBUG_RDPEI
+ WLog_DBG(TAG, "contactCount: %" PRIu32 "", frame->contactCount);
+ WLog_DBG(TAG, "frameOffset: 0x%016" PRIX64 "", frame->frameOffset);
+#endif
+ rdpei_write_2byte_unsigned(s,
+ frame->contactCount); /* contactCount (TWO_BYTE_UNSIGNED_INTEGER) */
+ /**
+ * the time offset from the previous frame (in microseconds).
+ * If this is the first frame being transmitted then this field MUST be set to zero.
+ */
+ rdpei_write_8byte_unsigned(s, frame->frameOffset *
+ 1000); /* frameOffset (EIGHT_BYTE_UNSIGNED_INTEGER) */
+
+ if (!Stream_EnsureRemainingCapacity(s, (size_t)frame->contactCount * 64))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ for (UINT32 index = 0; index < frame->contactCount; index++)
+ {
+ contact = &frame->contacts[index];
+ contact->fieldsPresent |= CONTACT_DATA_CONTACTRECT_PRESENT;
+ contact->contactRectLeft = contact->x - rectSize;
+ contact->contactRectTop = contact->y - rectSize;
+ contact->contactRectRight = contact->x + rectSize;
+ contact->contactRectBottom = contact->y + rectSize;
+#ifdef WITH_DEBUG_RDPEI
+ WLog_DBG(TAG, "contact[%" PRIu32 "].contactId: %" PRIu32 "", index, contact->contactId);
+ WLog_DBG(TAG, "contact[%" PRIu32 "].fieldsPresent: %" PRIu32 "", index,
+ contact->fieldsPresent);
+ WLog_DBG(TAG, "contact[%" PRIu32 "].x: %" PRId32 "", index, contact->x);
+ WLog_DBG(TAG, "contact[%" PRIu32 "].y: %" PRId32 "", index, contact->y);
+ WLog_DBG(TAG, "contact[%" PRIu32 "].contactFlags: 0x%08" PRIX32 "", index,
+ contact->contactFlags);
+ rdpei_print_contact_flags(contact->contactFlags);
+#endif
+ Stream_Write_UINT8(s, contact->contactId); /* contactId (1 byte) */
+ /* fieldsPresent (TWO_BYTE_UNSIGNED_INTEGER) */
+ rdpei_write_2byte_unsigned(s, contact->fieldsPresent);
+ rdpei_write_4byte_signed(s, contact->x); /* x (FOUR_BYTE_SIGNED_INTEGER) */
+ rdpei_write_4byte_signed(s, contact->y); /* y (FOUR_BYTE_SIGNED_INTEGER) */
+ /* contactFlags (FOUR_BYTE_UNSIGNED_INTEGER) */
+ rdpei_write_4byte_unsigned(s, contact->contactFlags);
+
+ if (contact->fieldsPresent & CONTACT_DATA_CONTACTRECT_PRESENT)
+ {
+ /* contactRectLeft (TWO_BYTE_SIGNED_INTEGER) */
+ rdpei_write_2byte_signed(s, contact->contactRectLeft);
+ /* contactRectTop (TWO_BYTE_SIGNED_INTEGER) */
+ rdpei_write_2byte_signed(s, contact->contactRectTop);
+ /* contactRectRight (TWO_BYTE_SIGNED_INTEGER) */
+ rdpei_write_2byte_signed(s, contact->contactRectRight);
+ /* contactRectBottom (TWO_BYTE_SIGNED_INTEGER) */
+ rdpei_write_2byte_signed(s, contact->contactRectBottom);
+ }
+
+ if (contact->fieldsPresent & CONTACT_DATA_ORIENTATION_PRESENT)
+ {
+ /* orientation (FOUR_BYTE_UNSIGNED_INTEGER) */
+ rdpei_write_4byte_unsigned(s, contact->orientation);
+ }
+
+ if (contact->fieldsPresent & CONTACT_DATA_PRESSURE_PRESENT)
+ {
+ /* pressure (FOUR_BYTE_UNSIGNED_INTEGER) */
+ rdpei_write_4byte_unsigned(s, contact->pressure);
+ }
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_send_touch_event_pdu(GENERIC_CHANNEL_CALLBACK* callback,
+ RDPINPUT_TOUCH_FRAME* frame)
+{
+ UINT status = 0;
+ wStream* s = NULL;
+ UINT32 pduLength = 0;
+ RDPEI_PLUGIN* rdpei = NULL;
+
+ WINPR_ASSERT(callback);
+
+ rdpei = (RDPEI_PLUGIN*)callback->plugin;
+ if (!rdpei || !rdpei->rdpcontext)
+ return ERROR_INTERNAL_ERROR;
+ if (freerdp_settings_get_bool(rdpei->rdpcontext->settings, FreeRDP_SuspendInput))
+ return CHANNEL_RC_OK;
+
+ if (!frame)
+ return ERROR_INTERNAL_ERROR;
+
+ pduLength = 64 + (frame->contactCount * 64);
+ s = Stream_New(NULL, pduLength);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Seek(s, RDPINPUT_HEADER_LENGTH);
+ /**
+ * the time that has elapsed (in milliseconds) from when the oldest touch frame
+ * was generated to when it was encoded for transmission by the client.
+ */
+ rdpei_write_4byte_unsigned(
+ s, (UINT32)frame->frameOffset); /* encodeTime (FOUR_BYTE_UNSIGNED_INTEGER) */
+ rdpei_write_2byte_unsigned(s, 1); /* (frameCount) TWO_BYTE_UNSIGNED_INTEGER */
+
+ if ((status = rdpei_write_touch_frame(s, frame)))
+ {
+ WLog_ERR(TAG, "rdpei_write_touch_frame failed with error %" PRIu32 "!", status);
+ Stream_Free(s, TRUE);
+ return status;
+ }
+
+ Stream_SealLength(s);
+ pduLength = Stream_Length(s);
+ status = rdpei_send_pdu(callback, s, EVENTID_TOUCH, pduLength);
+ Stream_Free(s, TRUE);
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_recv_sc_ready_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ UINT32 features = 0;
+ UINT32 protocolVersion = 0;
+ RDPEI_PLUGIN* rdpei = NULL;
+
+ if (!callback || !callback->plugin)
+ return ERROR_INTERNAL_ERROR;
+
+ rdpei = (RDPEI_PLUGIN*)callback->plugin;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+ Stream_Read_UINT32(s, protocolVersion); /* protocolVersion (4 bytes) */
+
+ if (protocolVersion >= RDPINPUT_PROTOCOL_V300)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+ }
+
+ if (Stream_GetRemainingLength(s) >= 4)
+ Stream_Read_UINT32(s, features);
+
+ if (rdpei->version > protocolVersion)
+ rdpei->version = protocolVersion;
+ rdpei->features = features;
+#if 0
+
+ if (protocolVersion != RDPINPUT_PROTOCOL_V10)
+ {
+ WLog_ERR(TAG, "Unknown [MS-RDPEI] protocolVersion: 0x%08"PRIX32"", protocolVersion);
+ return -1;
+ }
+
+#endif
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_recv_suspend_touch_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ UINT error = CHANNEL_RC_OK;
+ RdpeiClientContext* rdpei = NULL;
+
+ WINPR_UNUSED(s);
+
+ if (!callback || !callback->plugin)
+ return ERROR_INTERNAL_ERROR;
+ rdpei = (RdpeiClientContext*)callback->plugin->pInterface;
+ if (!rdpei)
+ return ERROR_INTERNAL_ERROR;
+
+ IFCALLRET(rdpei->SuspendTouch, error, rdpei);
+
+ if (error)
+ WLog_ERR(TAG, "rdpei->SuspendTouch failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_recv_resume_touch_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ RdpeiClientContext* rdpei = NULL;
+ UINT error = CHANNEL_RC_OK;
+ if (!s || !callback || !callback->plugin)
+ return ERROR_INTERNAL_ERROR;
+ rdpei = (RdpeiClientContext*)callback->plugin->pInterface;
+ if (!rdpei)
+ return ERROR_INTERNAL_ERROR;
+
+ IFCALLRET(rdpei->ResumeTouch, error, rdpei);
+
+ if (error)
+ WLog_ERR(TAG, "rdpei->ResumeTouch failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_recv_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ UINT16 eventId = 0;
+ UINT32 pduLength = 0;
+ UINT error = 0;
+
+ if (!s)
+ return ERROR_INTERNAL_ERROR;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, eventId); /* eventId (2 bytes) */
+ Stream_Read_UINT32(s, pduLength); /* pduLength (4 bytes) */
+#ifdef WITH_DEBUG_RDPEI
+ WLog_DBG(TAG, "rdpei_recv_pdu: eventId: %" PRIu16 " (%s) length: %" PRIu32 "", eventId,
+ rdpei_eventid_string(eventId), pduLength);
+#endif
+
+ switch (eventId)
+ {
+ case EVENTID_SC_READY:
+ if ((error = rdpei_recv_sc_ready_pdu(callback, s)))
+ {
+ WLog_ERR(TAG, "rdpei_recv_sc_ready_pdu failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ if ((error = rdpei_send_cs_ready_pdu(callback)))
+ {
+ WLog_ERR(TAG, "rdpei_send_cs_ready_pdu failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ break;
+
+ case EVENTID_SUSPEND_TOUCH:
+ if ((error = rdpei_recv_suspend_touch_pdu(callback, s)))
+ {
+ WLog_ERR(TAG, "rdpei_recv_suspend_touch_pdu failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ break;
+
+ case EVENTID_RESUME_TOUCH:
+ if ((error = rdpei_recv_resume_touch_pdu(callback, s)))
+ {
+ WLog_ERR(TAG, "rdpei_recv_resume_touch_pdu failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+ return rdpei_recv_pdu(callback, data);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_on_close(IWTSVirtualChannelCallback* pChannelCallback)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+ if (callback)
+ {
+ RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin;
+ if (rdpei && rdpei->base.listener_callback)
+ {
+ if (rdpei->base.listener_callback->channel_callback == callback)
+ rdpei->base.listener_callback->channel_callback = NULL;
+ }
+ }
+ free(callback);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Channel Client Interface
+ */
+
+static UINT32 rdpei_get_version(RdpeiClientContext* context)
+{
+ RDPEI_PLUGIN* rdpei = NULL;
+ if (!context || !context->handle)
+ return -1;
+ rdpei = (RDPEI_PLUGIN*)context->handle;
+ return rdpei->version;
+}
+
+static UINT32 rdpei_get_features(RdpeiClientContext* context)
+{
+ RDPEI_PLUGIN* rdpei = NULL;
+ if (!context || !context->handle)
+ return -1;
+ rdpei = (RDPEI_PLUGIN*)context->handle;
+ return rdpei->features;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpei_send_frame(RdpeiClientContext* context, RDPINPUT_TOUCH_FRAME* frame)
+{
+ UINT64 currentTime = GetTickCount64();
+ RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle;
+ GENERIC_CHANNEL_CALLBACK* callback = NULL;
+ UINT error = 0;
+
+ callback = rdpei->base.listener_callback->channel_callback;
+
+ /* Just ignore the event if the channel is not connected */
+ if (!callback)
+ return CHANNEL_RC_OK;
+
+ if (!rdpei->previousFrameTime && !rdpei->currentFrameTime)
+ {
+ rdpei->currentFrameTime = currentTime;
+ frame->frameOffset = 0;
+ }
+ else
+ {
+ rdpei->currentFrameTime = currentTime;
+ frame->frameOffset = rdpei->currentFrameTime - rdpei->previousFrameTime;
+ }
+
+ if ((error = rdpei_send_touch_event_pdu(callback, frame)))
+ {
+ WLog_ERR(TAG, "rdpei_send_touch_event_pdu failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ rdpei->previousFrameTime = rdpei->currentFrameTime;
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_add_contact(RdpeiClientContext* context, const RDPINPUT_CONTACT_DATA* contact)
+{
+ RDPINPUT_CONTACT_POINT* contactPoint = NULL;
+ RDPEI_PLUGIN* rdpei = NULL;
+ if (!context || !contact || !context->handle)
+ return ERROR_INTERNAL_ERROR;
+
+ rdpei = (RDPEI_PLUGIN*)context->handle;
+
+ EnterCriticalSection(&rdpei->lock);
+ contactPoint = &rdpei->contactPoints[contact->contactId];
+ contactPoint->data = *contact;
+ contactPoint->dirty = TRUE;
+ SetEvent(rdpei->event);
+ LeaveCriticalSection(&rdpei->lock);
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpei_touch_process(RdpeiClientContext* context, INT32 externalId, UINT32 contactFlags,
+ INT32 x, INT32 y, INT32* contactId, UINT32 fieldFlags, va_list ap)
+{
+ INT64 contactIdlocal = -1;
+ RDPINPUT_CONTACT_POINT* contactPoint = NULL;
+ RDPEI_PLUGIN* rdpei = NULL;
+ BOOL begin = 0;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!context || !contactId || !context->handle)
+ return ERROR_INTERNAL_ERROR;
+
+ rdpei = (RDPEI_PLUGIN*)context->handle;
+ /* Create a new contact point in an empty slot */
+ EnterCriticalSection(&rdpei->lock);
+ begin = contactFlags & RDPINPUT_CONTACT_FLAG_DOWN;
+ contactPoint = rdpei_contact(rdpei, externalId, !begin);
+ if (contactPoint)
+ contactIdlocal = contactPoint->contactId;
+ LeaveCriticalSection(&rdpei->lock);
+
+ if (contactIdlocal >= 0)
+ {
+ RDPINPUT_CONTACT_DATA contact = { 0 };
+ contact.x = x;
+ contact.y = y;
+ contact.contactId = contactIdlocal;
+ contact.contactFlags = contactFlags;
+ contact.fieldsPresent = fieldFlags;
+
+ if (fieldFlags & CONTACT_DATA_CONTACTRECT_PRESENT)
+ {
+ contact.contactRectLeft = va_arg(ap, INT32);
+ contact.contactRectTop = va_arg(ap, INT32);
+ contact.contactRectRight = va_arg(ap, INT32);
+ contact.contactRectBottom = va_arg(ap, INT32);
+ }
+ if (fieldFlags & CONTACT_DATA_ORIENTATION_PRESENT)
+ {
+ UINT32 p = va_arg(ap, UINT32);
+ if (p >= 360)
+ {
+ WLog_WARN(TAG,
+ "TouchContact %" PRIu32 ": Invalid orientation value %" PRIu32
+ "degree, clamping to 359 degree",
+ contactIdlocal, p);
+ p = 359;
+ }
+ contact.orientation = p;
+ }
+ if (fieldFlags & CONTACT_DATA_PRESSURE_PRESENT)
+ {
+ UINT32 p = va_arg(ap, UINT32);
+ if (p > 1024)
+ {
+ WLog_WARN(TAG,
+ "TouchContact %" PRIu32 ": Invalid pressure value %" PRIu32
+ ", clamping to 1024",
+ contactIdlocal, p);
+ p = 1024;
+ }
+ contact.pressure = p;
+ }
+
+ error = context->AddContact(context, &contact);
+ }
+
+ if (contactId)
+ *contactId = contactIdlocal;
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_touch_begin(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y,
+ INT32* contactId)
+{
+ UINT rc = 0;
+ va_list ap = { 0 };
+ rc = rdpei_touch_process(context, externalId,
+ RDPINPUT_CONTACT_FLAG_DOWN | RDPINPUT_CONTACT_FLAG_INRANGE |
+ RDPINPUT_CONTACT_FLAG_INCONTACT,
+ x, y, contactId, 0, ap);
+ return rc;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_touch_update(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y,
+ INT32* contactId)
+{
+ UINT rc = 0;
+ va_list ap = { 0 };
+ rc = rdpei_touch_process(context, externalId,
+ RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE |
+ RDPINPUT_CONTACT_FLAG_INCONTACT,
+ x, y, contactId, 0, ap);
+ return rc;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_touch_end(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y,
+ INT32* contactId)
+{
+ UINT error = 0;
+ va_list ap = { 0 };
+ error = rdpei_touch_process(context, externalId,
+ RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE |
+ RDPINPUT_CONTACT_FLAG_INCONTACT,
+ x, y, contactId, 0, ap);
+ if (error != CHANNEL_RC_OK)
+ return error;
+ error =
+ rdpei_touch_process(context, externalId, RDPINPUT_CONTACT_FLAG_UP, x, y, contactId, 0, ap);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_touch_cancel(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y,
+ INT32* contactId)
+{
+ UINT rc = 0;
+ va_list ap = { 0 };
+ rc = rdpei_touch_process(context, externalId,
+ RDPINPUT_CONTACT_FLAG_UP | RDPINPUT_CONTACT_FLAG_CANCELED, x, y,
+ contactId, 0, ap);
+ return rc;
+}
+
+static UINT rdpei_touch_raw_event(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y,
+ INT32* contactId, UINT32 flags, UINT32 fieldFlags, ...)
+{
+ UINT rc = 0;
+ va_list ap;
+ va_start(ap, fieldFlags);
+ rc = rdpei_touch_process(context, externalId, flags, x, y, contactId, fieldFlags, ap);
+ va_end(ap);
+ return rc;
+}
+
+static UINT rdpei_touch_raw_event_va(RdpeiClientContext* context, INT32 externalId, INT32 x,
+ INT32 y, INT32* contactId, UINT32 flags, UINT32 fieldFlags,
+ va_list args)
+{
+ return rdpei_touch_process(context, externalId, flags, x, y, contactId, fieldFlags, args);
+}
+
+static RDPINPUT_PEN_CONTACT_POINT* rdpei_pen_contact(RDPEI_PLUGIN* rdpei, INT32 externalId,
+ BOOL active)
+{
+ if (!rdpei)
+ return NULL;
+
+ for (UINT32 x = 0; x < rdpei->maxPenContacts; x++)
+ {
+ RDPINPUT_PEN_CONTACT_POINT* contact = &rdpei->penContactPoints[x];
+ if (active)
+ {
+ if (contact->active)
+ {
+ if (contact->externalId == externalId)
+ return contact;
+ }
+ }
+ else
+ {
+ if (!contact->active)
+ {
+ contact->externalId = externalId;
+ contact->active = TRUE;
+ return contact;
+ }
+ }
+ }
+ return NULL;
+}
+
+static UINT rdpei_add_pen(RdpeiClientContext* context, INT32 externalId,
+ const RDPINPUT_PEN_CONTACT* contact)
+{
+ RDPEI_PLUGIN* rdpei = NULL;
+ RDPINPUT_PEN_CONTACT_POINT* contactPoint = NULL;
+
+ if (!context || !contact || !context->handle)
+ return ERROR_INTERNAL_ERROR;
+
+ rdpei = (RDPEI_PLUGIN*)context->handle;
+
+ EnterCriticalSection(&rdpei->lock);
+ contactPoint = rdpei_pen_contact(rdpei, externalId, TRUE);
+ if (contactPoint)
+ {
+ contactPoint->data = *contact;
+ contactPoint->dirty = TRUE;
+ SetEvent(rdpei->event);
+ }
+ LeaveCriticalSection(&rdpei->lock);
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpei_pen_process(RdpeiClientContext* context, INT32 externalId, UINT32 contactFlags,
+ UINT32 fieldFlags, INT32 x, INT32 y, va_list ap)
+{
+ RDPINPUT_PEN_CONTACT_POINT* contactPoint = NULL;
+ RDPEI_PLUGIN* rdpei = NULL;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!context || !context->handle)
+ return ERROR_INTERNAL_ERROR;
+
+ rdpei = (RDPEI_PLUGIN*)context->handle;
+
+ EnterCriticalSection(&rdpei->lock);
+ // Start a new contact only when it is not active.
+ contactPoint = rdpei_pen_contact(rdpei, externalId, TRUE);
+ if (!contactPoint)
+ {
+ const UINT32 mask = RDPINPUT_CONTACT_FLAG_INRANGE;
+ if ((contactFlags & mask) == mask)
+ {
+ contactPoint = rdpei_pen_contact(rdpei, externalId, FALSE);
+ }
+ }
+ LeaveCriticalSection(&rdpei->lock);
+ if (contactPoint != NULL)
+ {
+ RDPINPUT_PEN_CONTACT contact = { 0 };
+
+ contact.x = x;
+ contact.y = y;
+ contact.fieldsPresent = fieldFlags;
+
+ contact.contactFlags = contactFlags;
+ if (fieldFlags & RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT)
+ contact.penFlags = va_arg(ap, UINT32);
+ if (fieldFlags & RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT)
+ contact.pressure = va_arg(ap, UINT32);
+ if (fieldFlags & RDPINPUT_PEN_CONTACT_ROTATION_PRESENT)
+ contact.rotation = va_arg(ap, UINT32);
+ if (fieldFlags & RDPINPUT_PEN_CONTACT_TILTX_PRESENT)
+ contact.tiltX = va_arg(ap, INT32);
+ if (fieldFlags & RDPINPUT_PEN_CONTACT_TILTY_PRESENT)
+ contact.tiltY = va_arg(ap, INT32);
+
+ error = context->AddPen(context, externalId, &contact);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_pen_begin(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags,
+ INT32 x, INT32 y, ...)
+{
+ UINT error = 0;
+ va_list ap;
+
+ va_start(ap, y);
+ error = rdpei_pen_process(context, externalId,
+ RDPINPUT_CONTACT_FLAG_DOWN | RDPINPUT_CONTACT_FLAG_INRANGE |
+ RDPINPUT_CONTACT_FLAG_INCONTACT,
+ fieldFlags, x, y, ap);
+ va_end(ap);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_pen_update(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags,
+ INT32 x, INT32 y, ...)
+{
+ UINT error = 0;
+ va_list ap;
+
+ va_start(ap, y);
+ error = rdpei_pen_process(context, externalId,
+ RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE |
+ RDPINPUT_CONTACT_FLAG_INCONTACT,
+ fieldFlags, x, y, ap);
+ va_end(ap);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_pen_end(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags, INT32 x,
+ INT32 y, ...)
+{
+ UINT error = 0;
+ va_list ap;
+ va_start(ap, y);
+ error = rdpei_pen_process(context, externalId,
+ RDPINPUT_CONTACT_FLAG_UP | RDPINPUT_CONTACT_FLAG_INRANGE, fieldFlags,
+ x, y, ap);
+ va_end(ap);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_pen_hover_begin(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags,
+ INT32 x, INT32 y, ...)
+{
+ UINT error = 0;
+ va_list ap;
+
+ va_start(ap, y);
+ error = rdpei_pen_process(context, externalId,
+ RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE,
+ fieldFlags, x, y, ap);
+ va_end(ap);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_pen_hover_update(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags,
+ INT32 x, INT32 y, ...)
+{
+ UINT error = 0;
+ va_list ap;
+
+ va_start(ap, y);
+ error = rdpei_pen_process(context, externalId,
+ RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE,
+ fieldFlags, x, y, ap);
+ va_end(ap);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_pen_hover_cancel(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags,
+ INT32 x, INT32 y, ...)
+{
+ UINT error = 0;
+ va_list ap;
+
+ va_start(ap, y);
+ error = rdpei_pen_process(context, externalId,
+ RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_CANCELED,
+ fieldFlags, x, y, ap);
+ va_end(ap);
+
+ return error;
+}
+
+static UINT rdpei_pen_raw_event(RdpeiClientContext* context, INT32 externalId, UINT32 contactFlags,
+ UINT32 fieldFlags, INT32 x, INT32 y, ...)
+{
+ UINT error = 0;
+ va_list ap;
+
+ va_start(ap, y);
+ error = rdpei_pen_process(context, externalId, contactFlags, fieldFlags, x, y, ap);
+ va_end(ap);
+ return error;
+}
+
+static UINT rdpei_pen_raw_event_va(RdpeiClientContext* context, INT32 externalId,
+ UINT32 contactFlags, UINT32 fieldFlags, INT32 x, INT32 y,
+ va_list args)
+{
+ return rdpei_pen_process(context, externalId, contactFlags, fieldFlags, x, y, args);
+}
+
+static UINT init_plugin_cb(GENERIC_DYNVC_PLUGIN* base, rdpContext* rcontext, rdpSettings* settings)
+{
+ RdpeiClientContext* context = NULL;
+ RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)base;
+
+ WINPR_ASSERT(base);
+ WINPR_UNUSED(settings);
+
+ rdpei->version = RDPINPUT_PROTOCOL_V300;
+ rdpei->currentFrameTime = 0;
+ rdpei->previousFrameTime = 0;
+ rdpei->maxTouchContacts = MAX_CONTACTS;
+ rdpei->maxPenContacts = MAX_PEN_CONTACTS;
+ rdpei->rdpcontext = rcontext;
+
+ InitializeCriticalSection(&rdpei->lock);
+ rdpei->event = CreateEventA(NULL, TRUE, FALSE, NULL);
+ if (!rdpei->event)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ context = (RdpeiClientContext*)calloc(1, sizeof(*context));
+ if (!context)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ context->clientFeaturesMask = UINT32_MAX;
+ context->handle = (void*)rdpei;
+ context->GetVersion = rdpei_get_version;
+ context->GetFeatures = rdpei_get_features;
+ context->AddContact = rdpei_add_contact;
+ context->TouchBegin = rdpei_touch_begin;
+ context->TouchUpdate = rdpei_touch_update;
+ context->TouchEnd = rdpei_touch_end;
+ context->TouchCancel = rdpei_touch_cancel;
+ context->TouchRawEvent = rdpei_touch_raw_event;
+ context->TouchRawEventVA = rdpei_touch_raw_event_va;
+ context->AddPen = rdpei_add_pen;
+ context->PenBegin = rdpei_pen_begin;
+ context->PenUpdate = rdpei_pen_update;
+ context->PenEnd = rdpei_pen_end;
+ context->PenHoverBegin = rdpei_pen_hover_begin;
+ context->PenHoverUpdate = rdpei_pen_hover_update;
+ context->PenHoverCancel = rdpei_pen_hover_cancel;
+ context->PenRawEvent = rdpei_pen_raw_event;
+ context->PenRawEventVA = rdpei_pen_raw_event_va;
+
+ rdpei->context = context;
+ rdpei->base.iface.pInterface = (void*)context;
+
+ rdpei->running = TRUE;
+ rdpei->thread = CreateThread(NULL, 0, rdpei_periodic_update, rdpei, 0, NULL);
+ if (!rdpei->thread)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static void terminate_plugin_cb(GENERIC_DYNVC_PLUGIN* base)
+{
+ RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)base;
+ WINPR_ASSERT(rdpei);
+
+ rdpei->running = FALSE;
+ if (rdpei->event)
+ SetEvent(rdpei->event);
+
+ if (rdpei->thread)
+ {
+ WaitForSingleObject(rdpei->thread, INFINITE);
+ CloseHandle(rdpei->thread);
+ }
+
+ if (rdpei->event)
+ CloseHandle(rdpei->event);
+
+ DeleteCriticalSection(&rdpei->lock);
+ free(rdpei->context);
+}
+
+static const IWTSVirtualChannelCallback geometry_callbacks = { rdpei_on_data_received,
+ NULL, /* Open */
+ rdpei_on_close, NULL };
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT rdpei_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
+{
+ return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, RDPEI_DVC_CHANNEL_NAME,
+ sizeof(RDPEI_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK),
+ &geometry_callbacks, init_plugin_cb, terminate_plugin_cb);
+}
diff --git a/channels/rdpei/client/rdpei_main.h b/channels/rdpei/client/rdpei_main.h
new file mode 100644
index 0000000..34b1b81
--- /dev/null
+++ b/channels/rdpei/client/rdpei_main.h
@@ -0,0 +1,89 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Input Virtual Channel Extension
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPEI_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_RDPEI_CLIENT_MAIN_H
+
+#include <freerdp/config.h>
+
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/log.h>
+
+#include <freerdp/channels/rdpei.h>
+#include <freerdp/client/rdpei.h>
+
+#define TAG CHANNELS_TAG("rdpei.client")
+
+/**
+ * Touch Contact State Transitions
+ *
+ * ENGAGED -> UPDATE | INRANGE | INCONTACT -> ENGAGED
+ * ENGAGED -> UP | INRANGE -> HOVERING
+ * ENGAGED -> UP -> OUT_OF_RANGE
+ * ENGAGED -> UP | CANCELED -> OUT_OF_RANGE
+ *
+ * HOVERING -> UPDATE | INRANGE -> HOVERING
+ * HOVERING -> DOWN | INRANGE | INCONTACT -> ENGAGED
+ * HOVERING -> UPDATE -> OUT_OF_RANGE
+ * HOVERING -> UPDATE | CANCELED -> OUT_OF_RANGE
+ *
+ * OUT_OF_RANGE -> DOWN | INRANGE | INCONTACT -> ENGAGED
+ * OUT_OF_RANGE -> UPDATE | INRANGE -> HOVERING
+ *
+ * When a contact is in the "hovering" or "engaged" state, it is referred to as being "active".
+ * "Hovering" contacts are in range of the digitizer, while "engaged" contacts are in range of
+ * the digitizer and in contact with the digitizer surface. MS-RDPEI remotes only active contacts
+ * and contacts that are transitioning to the "out of range" state; see section 2.2.3.3.1.1 for
+ * an enumeration of valid state flags combinations.
+ *
+ * When transitioning from the "engaged" state to the "hovering" state, or from the "engaged"
+ * state to the "out of range" state, the contact position cannot change; it is only allowed
+ * to change after the transition has taken place.
+ *
+ */
+
+typedef struct
+{
+ BOOL dirty;
+ BOOL active;
+ UINT32 contactId;
+ INT32 externalId;
+ RDPINPUT_CONTACT_DATA data;
+} RDPINPUT_CONTACT_POINT;
+
+typedef struct
+{
+ BOOL dirty;
+ BOOL active;
+ INT32 externalId;
+ RDPINPUT_PEN_CONTACT data;
+} RDPINPUT_PEN_CONTACT_POINT;
+
+#ifdef WITH_DEBUG_DVC
+#define DEBUG_DVC(...) WLog_DBG(TAG, __VA_ARGS__)
+#else
+#define DEBUG_DVC(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#endif /* FREERDP_CHANNEL_RDPEI_CLIENT_MAIN_H */
diff --git a/channels/rdpei/rdpei_common.c b/channels/rdpei/rdpei_common.c
new file mode 100644
index 0000000..7594672
--- /dev/null
+++ b/channels/rdpei/rdpei_common.c
@@ -0,0 +1,641 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Input Virtual Channel Extension
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 David Fort <contact@hardening-consulting.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+
+#include "rdpei_common.h"
+
+#include <freerdp/log.h>
+
+#define TAG FREERDP_TAG("channels.rdpei.common")
+
+BOOL rdpei_read_2byte_unsigned(wStream* s, UINT16* value)
+{
+ BYTE byte = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte & 0x80)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ *value = (byte & 0x7F) << 8;
+ Stream_Read_UINT8(s, byte);
+ *value |= byte;
+ }
+ else
+ {
+ *value = (byte & 0x7F);
+ }
+
+ return TRUE;
+}
+
+BOOL rdpei_write_2byte_unsigned(wStream* s, UINT16 value)
+{
+ BYTE byte = 0;
+
+ if (!Stream_EnsureRemainingCapacity(s, 2))
+ return FALSE;
+
+ if (value > 0x7FFF)
+ return FALSE;
+
+ if (value >= 0x7F)
+ {
+ byte = ((value & 0x7F00) >> 8);
+ Stream_Write_UINT8(s, byte | 0x80);
+ byte = (value & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ }
+ else
+ {
+ byte = (value & 0x7F);
+ Stream_Write_UINT8(s, byte);
+ }
+
+ return TRUE;
+}
+
+BOOL rdpei_read_2byte_signed(wStream* s, INT16* value)
+{
+ BYTE byte = 0;
+ BOOL negative = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ negative = (byte & 0x40) ? TRUE : FALSE;
+
+ *value = (byte & 0x3F);
+
+ if (byte & 0x80)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+ *value = (*value << 8) | byte;
+ }
+
+ if (negative)
+ *value *= -1;
+
+ return TRUE;
+}
+
+BOOL rdpei_write_2byte_signed(wStream* s, INT16 value)
+{
+ BYTE byte = 0;
+ BOOL negative = FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, 2))
+ return FALSE;
+
+ if (value < 0)
+ {
+ negative = TRUE;
+ value *= -1;
+ }
+
+ if (value > 0x3FFF)
+ return FALSE;
+
+ if (value >= 0x3F)
+ {
+ byte = ((value & 0x3F00) >> 8);
+
+ if (negative)
+ byte |= 0x40;
+
+ Stream_Write_UINT8(s, byte | 0x80);
+ byte = (value & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ }
+ else
+ {
+ byte = (value & 0x3F);
+
+ if (negative)
+ byte |= 0x40;
+
+ Stream_Write_UINT8(s, byte);
+ }
+
+ return TRUE;
+}
+
+BOOL rdpei_read_4byte_unsigned(wStream* s, UINT32* value)
+{
+ BYTE byte = 0;
+ BYTE count = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ count = (byte & 0xC0) >> 6;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, count))
+ return FALSE;
+
+ switch (count)
+ {
+ case 0:
+ *value = (byte & 0x3F);
+ break;
+
+ case 1:
+ *value = (byte & 0x3F) << 8;
+ Stream_Read_UINT8(s, byte);
+ *value |= byte;
+ break;
+
+ case 2:
+ *value = (byte & 0x3F) << 16;
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 8);
+ Stream_Read_UINT8(s, byte);
+ *value |= byte;
+ break;
+
+ case 3:
+ *value = (byte & 0x3F) << 24;
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 16);
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 8);
+ Stream_Read_UINT8(s, byte);
+ *value |= byte;
+ break;
+
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+BOOL rdpei_write_4byte_unsigned(wStream* s, UINT32 value)
+{
+ BYTE byte = 0;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return FALSE;
+
+ if (value <= 0x3FUL)
+ {
+ Stream_Write_UINT8(s, value);
+ }
+ else if (value <= 0x3FFFUL)
+ {
+ byte = (value >> 8) & 0x3F;
+ Stream_Write_UINT8(s, byte | 0x40);
+ byte = (value & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ }
+ else if (value <= 0x3FFFFFUL)
+ {
+ byte = (value >> 16) & 0x3F;
+ Stream_Write_UINT8(s, byte | 0x80);
+ byte = (value >> 8) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (value & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ }
+ else if (value <= 0x3FFFFFFFUL)
+ {
+ byte = (value >> 24) & 0x3F;
+ Stream_Write_UINT8(s, byte | 0xC0);
+ byte = (value >> 16) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 8) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (value & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL rdpei_read_4byte_signed(wStream* s, INT32* value)
+{
+ BYTE byte = 0;
+ BYTE count = 0;
+ BOOL negative = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ count = (byte & 0xC0) >> 6;
+ negative = (byte & 0x20) ? TRUE : FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, count))
+ return FALSE;
+
+ switch (count)
+ {
+ case 0:
+ *value = (byte & 0x1F);
+ break;
+
+ case 1:
+ *value = (byte & 0x1F) << 8;
+ Stream_Read_UINT8(s, byte);
+ *value |= byte;
+ break;
+
+ case 2:
+ *value = (byte & 0x1F) << 16;
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 8);
+ Stream_Read_UINT8(s, byte);
+ *value |= byte;
+ break;
+
+ case 3:
+ *value = (byte & 0x1F) << 24;
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 16);
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 8);
+ Stream_Read_UINT8(s, byte);
+ *value |= byte;
+ break;
+
+ default:
+ break;
+ }
+
+ if (negative)
+ *value *= -1;
+
+ return TRUE;
+}
+
+BOOL rdpei_write_4byte_signed(wStream* s, INT32 value)
+{
+ BYTE byte = 0;
+ BOOL negative = FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return FALSE;
+
+ if (value < 0)
+ {
+ negative = TRUE;
+ value *= -1;
+ }
+
+ if (value <= 0x1FL)
+ {
+ byte = value & 0x1F;
+
+ if (negative)
+ byte |= 0x20;
+
+ Stream_Write_UINT8(s, byte);
+ }
+ else if (value <= 0x1FFFL)
+ {
+ byte = (value >> 8) & 0x1F;
+
+ if (negative)
+ byte |= 0x20;
+
+ Stream_Write_UINT8(s, byte | 0x40);
+ byte = (value & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ }
+ else if (value <= 0x1FFFFFL)
+ {
+ byte = (value >> 16) & 0x1F;
+
+ if (negative)
+ byte |= 0x20;
+
+ Stream_Write_UINT8(s, byte | 0x80);
+ byte = (value >> 8) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (value & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ }
+ else if (value <= 0x1FFFFFFFL)
+ {
+ byte = (value >> 24) & 0x1F;
+
+ if (negative)
+ byte |= 0x20;
+
+ Stream_Write_UINT8(s, byte | 0xC0);
+ byte = (value >> 16) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 8) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (value & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL rdpei_read_8byte_unsigned(wStream* s, UINT64* value)
+{
+ UINT64 byte = 0;
+ BYTE count = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ count = (byte & 0xE0) >> 5;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, count))
+ return FALSE;
+
+ switch (count)
+ {
+ case 0:
+ *value = (byte & 0x1F);
+ break;
+
+ case 1:
+ *value = (byte & 0x1FU) << 8U;
+ Stream_Read_UINT8(s, byte);
+ *value |= byte;
+ break;
+
+ case 2:
+ *value = (byte & 0x1FU) << 16U;
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 8U);
+ Stream_Read_UINT8(s, byte);
+ *value |= byte;
+ break;
+
+ case 3:
+ *value = (byte & 0x1FU) << 24U;
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 16U);
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 8U);
+ Stream_Read_UINT8(s, byte);
+ *value |= byte;
+ break;
+
+ case 4:
+ *value = ((byte & 0x1FU)) << 32U;
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 24U);
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 16U);
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 8U);
+ Stream_Read_UINT8(s, byte);
+ *value |= byte;
+ break;
+
+ case 5:
+ *value = ((byte & 0x1FU)) << 40U;
+ Stream_Read_UINT8(s, byte);
+ *value |= ((byte) << 32U);
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 24U);
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 16U);
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 8U);
+ Stream_Read_UINT8(s, byte);
+ *value |= byte;
+ break;
+
+ case 6:
+ *value = ((byte & 0x1FU)) << 48U;
+ Stream_Read_UINT8(s, byte);
+ *value |= ((byte) << 40U);
+ Stream_Read_UINT8(s, byte);
+ *value |= ((byte) << 32U);
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 24U);
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 16U);
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 8U);
+ Stream_Read_UINT8(s, byte);
+ *value |= byte;
+ break;
+
+ case 7:
+ *value = ((byte & 0x1FU)) << 56U;
+ Stream_Read_UINT8(s, byte);
+ *value |= ((byte) << 48U);
+ Stream_Read_UINT8(s, byte);
+ *value |= ((byte) << 40U);
+ Stream_Read_UINT8(s, byte);
+ *value |= ((byte) << 32U);
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 24U);
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 16U);
+ Stream_Read_UINT8(s, byte);
+ *value |= (byte << 8U);
+ Stream_Read_UINT8(s, byte);
+ *value |= byte;
+ break;
+
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+BOOL rdpei_write_8byte_unsigned(wStream* s, UINT64 value)
+{
+ BYTE byte = 0;
+
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return FALSE;
+
+ if (value <= 0x1FULL)
+ {
+ byte = value & 0x1F;
+ Stream_Write_UINT8(s, byte);
+ }
+ else if (value <= 0x1FFFULL)
+ {
+ byte = (value >> 8) & 0x1F;
+ byte |= (1 << 5);
+ Stream_Write_UINT8(s, byte);
+ byte = (value & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ }
+ else if (value <= 0x1FFFFFULL)
+ {
+ byte = (value >> 16) & 0x1F;
+ byte |= (2 << 5);
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 8) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (value & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ }
+ else if (value <= 0x1FFFFFFFULL)
+ {
+ byte = (value >> 24) & 0x1F;
+ byte |= (3 << 5);
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 16) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 8) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (value & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ }
+ else if (value <= 0x1FFFFFFFFFULL)
+ {
+ byte = (value >> 32) & 0x1F;
+ byte |= (4 << 5);
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 24) & 0x1F;
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 16) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 8) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (value & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ }
+ else if (value <= 0x1FFFFFFFFFFFULL)
+ {
+ byte = (value >> 40) & 0x1F;
+ byte |= (5 << 5);
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 32) & 0x1F;
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 24) & 0x1F;
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 16) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 8) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (value & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ }
+ else if (value <= 0x1FFFFFFFFFFFFFULL)
+ {
+ byte = (value >> 48) & 0x1F;
+ byte |= (6 << 5);
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 40) & 0x1F;
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 32) & 0x1F;
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 24) & 0x1F;
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 16) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 8) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (value & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ }
+ else if (value <= 0x1FFFFFFFFFFFFFFFULL)
+ {
+ byte = (value >> 56) & 0x1F;
+ byte |= (7 << 5);
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 48) & 0x1F;
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 40) & 0x1F;
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 32) & 0x1F;
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 24) & 0x1F;
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 16) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (value >> 8) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (value & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void touch_event_reset(RDPINPUT_TOUCH_EVENT* event)
+{
+ for (UINT16 i = 0; i < event->frameCount; i++)
+ touch_frame_reset(&event->frames[i]);
+
+ free(event->frames);
+ event->frames = NULL;
+ event->frameCount = 0;
+}
+
+void touch_frame_reset(RDPINPUT_TOUCH_FRAME* frame)
+{
+ free(frame->contacts);
+ frame->contacts = NULL;
+ frame->contactCount = 0;
+}
+
+void pen_event_reset(RDPINPUT_PEN_EVENT* event)
+{
+ for (UINT16 i = 0; i < event->frameCount; i++)
+ pen_frame_reset(&event->frames[i]);
+
+ free(event->frames);
+ event->frames = NULL;
+ event->frameCount = 0;
+}
+
+void pen_frame_reset(RDPINPUT_PEN_FRAME* frame)
+{
+ free(frame->contacts);
+ frame->contacts = NULL;
+ frame->contactCount = 0;
+}
diff --git a/channels/rdpei/rdpei_common.h b/channels/rdpei/rdpei_common.h
new file mode 100644
index 0000000..3a5362f
--- /dev/null
+++ b/channels/rdpei/rdpei_common.h
@@ -0,0 +1,57 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Input Virtual Channel Extension
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 David Fort <contact@hardening-consulting.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPEI_COMMON_H
+#define FREERDP_CHANNEL_RDPEI_COMMON_H
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+#include <freerdp/channels/rdpei.h>
+
+/** @brief input event ids */
+enum
+{
+ EVENTID_SC_READY = 0x0001,
+ EVENTID_CS_READY = 0x0002,
+ EVENTID_TOUCH = 0x0003,
+ EVENTID_SUSPEND_TOUCH = 0x0004,
+ EVENTID_RESUME_TOUCH = 0x0005,
+ EVENTID_DISMISS_HOVERING_CONTACT = 0x0006,
+ EVENTID_PEN = 0x0008
+};
+
+BOOL rdpei_read_2byte_unsigned(wStream* s, UINT16* value);
+BOOL rdpei_write_2byte_unsigned(wStream* s, UINT16 value);
+BOOL rdpei_read_2byte_signed(wStream* s, INT16* value);
+BOOL rdpei_write_2byte_signed(wStream* s, INT16 value);
+BOOL rdpei_read_4byte_unsigned(wStream* s, UINT32* value);
+BOOL rdpei_write_4byte_unsigned(wStream* s, UINT32 value);
+BOOL rdpei_read_4byte_signed(wStream* s, INT32* value);
+BOOL rdpei_write_4byte_signed(wStream* s, INT32 value);
+BOOL rdpei_read_8byte_unsigned(wStream* s, UINT64* value);
+BOOL rdpei_write_8byte_unsigned(wStream* s, UINT64 value);
+
+void touch_event_reset(RDPINPUT_TOUCH_EVENT* event);
+void touch_frame_reset(RDPINPUT_TOUCH_FRAME* frame);
+
+void pen_event_reset(RDPINPUT_PEN_EVENT* event);
+void pen_frame_reset(RDPINPUT_PEN_FRAME* frame);
+
+#endif /* FREERDP_CHANNEL_RDPEI_COMMON_H */
diff --git a/channels/rdpei/server/CMakeLists.txt b/channels/rdpei/server/CMakeLists.txt
new file mode 100644
index 0000000..21c96ee
--- /dev/null
+++ b/channels/rdpei/server/CMakeLists.txt
@@ -0,0 +1,32 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2014 Thincast Technologies Gmbh.
+# Copyright 2014 David FORT <contact@hardening-consulting.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_server("rdpei")
+
+set(${MODULE_PREFIX}_SRCS
+ rdpei_main.c
+ rdpei_main.h
+ ../rdpei_common.c
+ ../rdpei_common.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+)
+
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry")
diff --git a/channels/rdpei/server/rdpei_main.c b/channels/rdpei/server/rdpei_main.c
new file mode 100644
index 0000000..c87a045
--- /dev/null
+++ b/channels/rdpei/server/rdpei_main.c
@@ -0,0 +1,720 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Extended Input channel server-side implementation
+ *
+ * Copyright 2014 Thincast Technologies Gmbh.
+ * Copyright 2014 David FORT <contact@hardening-consulting.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+
+#include "rdpei_main.h"
+#include "../rdpei_common.h"
+#include <freerdp/channels/rdpei.h>
+#include <freerdp/server/rdpei.h>
+
+enum RdpEiState
+{
+ STATE_INITIAL,
+ STATE_WAITING_CLIENT_READY,
+ STATE_WAITING_FRAME,
+ STATE_SUSPENDED,
+};
+
+struct s_rdpei_server_private
+{
+ HANDLE channelHandle;
+ HANDLE eventHandle;
+
+ UINT32 expectedBytes;
+ BOOL waitingHeaders;
+ wStream* inputStream;
+ wStream* outputStream;
+
+ UINT16 currentMsgType;
+
+ RDPINPUT_TOUCH_EVENT touchEvent;
+ RDPINPUT_PEN_EVENT penEvent;
+
+ enum RdpEiState automataState;
+};
+
+RdpeiServerContext* rdpei_server_context_new(HANDLE vcm)
+{
+ RdpeiServerContext* ret = calloc(1, sizeof(*ret));
+ RdpeiServerPrivate* priv = NULL;
+
+ if (!ret)
+ return NULL;
+
+ ret->priv = priv = calloc(1, sizeof(*ret->priv));
+ if (!priv)
+ goto fail;
+
+ priv->inputStream = Stream_New(NULL, 256);
+ if (!priv->inputStream)
+ goto fail;
+
+ priv->outputStream = Stream_New(NULL, 200);
+ if (!priv->inputStream)
+ goto fail;
+
+ ret->vcm = vcm;
+ rdpei_server_context_reset(ret);
+ return ret;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ rdpei_server_context_free(ret);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpei_server_init(RdpeiServerContext* context)
+{
+ void* buffer = NULL;
+ DWORD bytesReturned = 0;
+ RdpeiServerPrivate* priv = context->priv;
+ UINT32 channelId = 0;
+ BOOL status = TRUE;
+
+ priv->channelHandle = WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, RDPEI_DVC_CHANNEL_NAME,
+ WTS_CHANNEL_OPTION_DYNAMIC);
+ if (!priv->channelHandle)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed!");
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+ }
+
+ channelId = WTSChannelGetIdByHandle(priv->channelHandle);
+
+ IFCALLRET(context->onChannelIdAssigned, status, context, channelId);
+ if (!status)
+ {
+ WLog_ERR(TAG, "context->onChannelIdAssigned failed!");
+ goto out_close;
+ }
+
+ if (!WTSVirtualChannelQuery(priv->channelHandle, WTSVirtualEventHandle, &buffer,
+ &bytesReturned) ||
+ (bytesReturned != sizeof(HANDLE)))
+ {
+ WLog_ERR(TAG,
+ "WTSVirtualChannelQuery failed or invalid invalid returned size(%" PRIu32 ")!",
+ bytesReturned);
+ if (buffer)
+ WTSFreeMemory(buffer);
+ goto out_close;
+ }
+ CopyMemory(&priv->eventHandle, buffer, sizeof(HANDLE));
+ WTSFreeMemory(buffer);
+
+ return CHANNEL_RC_OK;
+
+out_close:
+ WTSVirtualChannelClose(priv->channelHandle);
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+}
+
+void rdpei_server_context_reset(RdpeiServerContext* context)
+{
+ RdpeiServerPrivate* priv = context->priv;
+
+ priv->channelHandle = INVALID_HANDLE_VALUE;
+ priv->expectedBytes = RDPINPUT_HEADER_LENGTH;
+ priv->waitingHeaders = TRUE;
+ priv->automataState = STATE_INITIAL;
+ Stream_SetPosition(priv->inputStream, 0);
+}
+
+void rdpei_server_context_free(RdpeiServerContext* context)
+{
+ RdpeiServerPrivate* priv = NULL;
+
+ if (!context)
+ return;
+ priv = context->priv;
+ if (priv)
+ {
+ if (priv->channelHandle != INVALID_HANDLE_VALUE)
+ WTSVirtualChannelClose(priv->channelHandle);
+ Stream_Free(priv->inputStream, TRUE);
+ }
+ free(priv);
+ free(context);
+}
+
+HANDLE rdpei_server_get_event_handle(RdpeiServerContext* context)
+{
+ return context->priv->eventHandle;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT read_cs_ready_message(RdpeiServerContext* context, wStream* s)
+{
+ UINT error = CHANNEL_RC_OK;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 10))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, context->protocolFlags);
+ Stream_Read_UINT32(s, context->clientVersion);
+ Stream_Read_UINT16(s, context->maxTouchPoints);
+
+ switch (context->clientVersion)
+ {
+ case RDPINPUT_PROTOCOL_V10:
+ case RDPINPUT_PROTOCOL_V101:
+ case RDPINPUT_PROTOCOL_V200:
+ case RDPINPUT_PROTOCOL_V300:
+ break;
+ default:
+ WLog_ERR(TAG, "unhandled RPDEI protocol version 0x%" PRIx32 "", context->clientVersion);
+ break;
+ }
+
+ IFCALLRET(context->onClientReady, error, context);
+ if (error)
+ WLog_ERR(TAG, "context->onClientReady failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT read_touch_contact_data(RdpeiServerContext* context, wStream* s,
+ RDPINPUT_CONTACT_DATA* contactData)
+{
+ WINPR_UNUSED(context);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT8(s, contactData->contactId);
+ if (!rdpei_read_2byte_unsigned(s, &contactData->fieldsPresent) ||
+ !rdpei_read_4byte_signed(s, &contactData->x) ||
+ !rdpei_read_4byte_signed(s, &contactData->y) ||
+ !rdpei_read_4byte_unsigned(s, &contactData->contactFlags))
+ {
+ WLog_ERR(TAG, "rdpei_read_ failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (contactData->fieldsPresent & CONTACT_DATA_CONTACTRECT_PRESENT)
+ {
+ if (!rdpei_read_2byte_signed(s, &contactData->contactRectLeft) ||
+ !rdpei_read_2byte_signed(s, &contactData->contactRectTop) ||
+ !rdpei_read_2byte_signed(s, &contactData->contactRectRight) ||
+ !rdpei_read_2byte_signed(s, &contactData->contactRectBottom))
+ {
+ WLog_ERR(TAG, "rdpei_read_ failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+
+ if ((contactData->fieldsPresent & CONTACT_DATA_ORIENTATION_PRESENT) &&
+ !rdpei_read_4byte_unsigned(s, &contactData->orientation))
+ {
+ WLog_ERR(TAG, "rdpei_read_ failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if ((contactData->fieldsPresent & CONTACT_DATA_PRESSURE_PRESENT) &&
+ !rdpei_read_4byte_unsigned(s, &contactData->pressure))
+ {
+ WLog_ERR(TAG, "rdpei_read_ failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT read_pen_contact(RdpeiServerContext* context, wStream* s,
+ RDPINPUT_PEN_CONTACT* contactData)
+{
+ WINPR_UNUSED(context);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT8(s, contactData->deviceId);
+ if (!rdpei_read_2byte_unsigned(s, &contactData->fieldsPresent) ||
+ !rdpei_read_4byte_signed(s, &contactData->x) ||
+ !rdpei_read_4byte_signed(s, &contactData->y) ||
+ !rdpei_read_4byte_unsigned(s, &contactData->contactFlags))
+ {
+ WLog_ERR(TAG, "rdpei_read_ failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT)
+ {
+ if (!rdpei_read_4byte_unsigned(s, &contactData->penFlags))
+ return ERROR_INVALID_DATA;
+ }
+ if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT)
+ {
+ if (!rdpei_read_4byte_unsigned(s, &contactData->pressure))
+ return ERROR_INVALID_DATA;
+ }
+ if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_ROTATION_PRESENT)
+ {
+ if (!rdpei_read_2byte_unsigned(s, &contactData->rotation))
+ return ERROR_INVALID_DATA;
+ }
+ if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTX_PRESENT)
+ {
+ if (!rdpei_read_2byte_signed(s, &contactData->tiltX))
+ return ERROR_INVALID_DATA;
+ }
+ if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTY_PRESENT)
+ {
+ if (!rdpei_read_2byte_signed(s, &contactData->tiltY))
+ return ERROR_INVALID_DATA;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT read_touch_frame(RdpeiServerContext* context, wStream* s, RDPINPUT_TOUCH_FRAME* frame)
+{
+ RDPINPUT_CONTACT_DATA* contact = NULL;
+ UINT error = 0;
+
+ if (!rdpei_read_2byte_unsigned(s, &frame->contactCount) ||
+ !rdpei_read_8byte_unsigned(s, &frame->frameOffset))
+ {
+ WLog_ERR(TAG, "rdpei_read_ failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ frame->contacts = contact = calloc(frame->contactCount, sizeof(RDPINPUT_CONTACT_DATA));
+ if (!frame->contacts)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ for (UINT32 i = 0; i < frame->contactCount; i++, contact++)
+ {
+ if ((error = read_touch_contact_data(context, s, contact)))
+ {
+ WLog_ERR(TAG, "read_touch_contact_data failed with error %" PRIu32 "!", error);
+ frame->contactCount = i;
+ touch_frame_reset(frame);
+ return error;
+ }
+ }
+ return CHANNEL_RC_OK;
+}
+
+static UINT read_pen_frame(RdpeiServerContext* context, wStream* s, RDPINPUT_PEN_FRAME* frame)
+{
+ RDPINPUT_PEN_CONTACT* contact = NULL;
+ UINT error = 0;
+
+ if (!rdpei_read_2byte_unsigned(s, &frame->contactCount) ||
+ !rdpei_read_8byte_unsigned(s, &frame->frameOffset))
+ {
+ WLog_ERR(TAG, "rdpei_read_ failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ frame->contacts = contact = calloc(frame->contactCount, sizeof(RDPINPUT_PEN_CONTACT));
+ if (!frame->contacts)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ for (UINT32 i = 0; i < frame->contactCount; i++, contact++)
+ {
+ if ((error = read_pen_contact(context, s, contact)))
+ {
+ WLog_ERR(TAG, "read_touch_contact_data failed with error %" PRIu32 "!", error);
+ frame->contactCount = i;
+ pen_frame_reset(frame);
+ return error;
+ }
+ }
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT read_touch_event(RdpeiServerContext* context, wStream* s)
+{
+ UINT16 frameCount = 0;
+ RDPINPUT_TOUCH_EVENT* event = &context->priv->touchEvent;
+ RDPINPUT_TOUCH_FRAME* frame = NULL;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!rdpei_read_4byte_unsigned(s, &event->encodeTime) ||
+ !rdpei_read_2byte_unsigned(s, &frameCount))
+ {
+ WLog_ERR(TAG, "rdpei_read_ failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ event->frameCount = frameCount;
+ event->frames = frame = calloc(event->frameCount, sizeof(RDPINPUT_TOUCH_FRAME));
+ if (!event->frames)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ for (UINT32 i = 0; i < frameCount; i++, frame++)
+ {
+ if ((error = read_touch_frame(context, s, frame)))
+ {
+ WLog_ERR(TAG, "read_touch_contact_data failed with error %" PRIu32 "!", error);
+ event->frameCount = i;
+ goto out_cleanup;
+ }
+ }
+
+ IFCALLRET(context->onTouchEvent, error, context, event);
+ if (error)
+ WLog_ERR(TAG, "context->onTouchEvent failed with error %" PRIu32 "", error);
+
+out_cleanup:
+ touch_event_reset(event);
+ return error;
+}
+
+static UINT read_pen_event(RdpeiServerContext* context, wStream* s)
+{
+ UINT16 frameCount = 0;
+ RDPINPUT_PEN_EVENT* event = &context->priv->penEvent;
+ RDPINPUT_PEN_FRAME* frame = NULL;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!rdpei_read_4byte_unsigned(s, &event->encodeTime) ||
+ !rdpei_read_2byte_unsigned(s, &frameCount))
+ {
+ WLog_ERR(TAG, "rdpei_read_ failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ event->frameCount = frameCount;
+ event->frames = frame = calloc(event->frameCount, sizeof(RDPINPUT_PEN_FRAME));
+ if (!event->frames)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ for (UINT32 i = 0; i < frameCount; i++, frame++)
+ {
+ if ((error = read_pen_frame(context, s, frame)))
+ {
+ WLog_ERR(TAG, "read_pen_frame failed with error %" PRIu32 "!", error);
+ event->frameCount = i;
+ goto out_cleanup;
+ }
+ }
+
+ IFCALLRET(context->onPenEvent, error, context, event);
+ if (error)
+ WLog_ERR(TAG, "context->onPenEvent failed with error %" PRIu32 "", error);
+
+out_cleanup:
+ pen_event_reset(event);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT read_dismiss_hovering_contact(RdpeiServerContext* context, wStream* s)
+{
+ BYTE contactId = 0;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT8(s, contactId);
+
+ IFCALLRET(context->onTouchReleased, error, context, contactId);
+ if (error)
+ WLog_ERR(TAG, "context->onTouchReleased failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpei_server_handle_messages(RdpeiServerContext* context)
+{
+ DWORD bytesReturned = 0;
+ RdpeiServerPrivate* priv = context->priv;
+ wStream* s = priv->inputStream;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!WTSVirtualChannelRead(priv->channelHandle, 0, Stream_Pointer(s), priv->expectedBytes,
+ &bytesReturned))
+ {
+ if (GetLastError() == ERROR_NO_DATA)
+ return ERROR_READ_FAULT;
+
+ WLog_DBG(TAG, "channel connection closed");
+ return CHANNEL_RC_OK;
+ }
+ priv->expectedBytes -= bytesReturned;
+ Stream_Seek(s, bytesReturned);
+
+ if (priv->expectedBytes)
+ return CHANNEL_RC_OK;
+
+ Stream_SealLength(s);
+ Stream_SetPosition(s, 0);
+
+ if (priv->waitingHeaders)
+ {
+ UINT32 pduLen = 0;
+
+ /* header case */
+ Stream_Read_UINT16(s, priv->currentMsgType);
+ Stream_Read_UINT16(s, pduLen);
+
+ if (pduLen < RDPINPUT_HEADER_LENGTH)
+ {
+ WLog_ERR(TAG, "invalid pduLength %" PRIu32 "", pduLen);
+ return ERROR_INVALID_DATA;
+ }
+ priv->expectedBytes = pduLen - RDPINPUT_HEADER_LENGTH;
+ priv->waitingHeaders = FALSE;
+ Stream_SetPosition(s, 0);
+ if (priv->expectedBytes)
+ {
+ if (!Stream_EnsureCapacity(s, priv->expectedBytes))
+ {
+ WLog_ERR(TAG, "Stream_EnsureCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ return CHANNEL_RC_OK;
+ }
+ }
+
+ /* when here we have the header + the body */
+ switch (priv->currentMsgType)
+ {
+ case EVENTID_CS_READY:
+ if (priv->automataState != STATE_WAITING_CLIENT_READY)
+ {
+ WLog_ERR(TAG, "not expecting a CS_READY packet in this state(%d)",
+ priv->automataState);
+ return ERROR_INVALID_STATE;
+ }
+
+ if ((error = read_cs_ready_message(context, s)))
+ {
+ WLog_ERR(TAG, "read_cs_ready_message failed with error %" PRIu32 "", error);
+ return error;
+ }
+ break;
+
+ case EVENTID_TOUCH:
+ if ((error = read_touch_event(context, s)))
+ {
+ WLog_ERR(TAG, "read_touch_event failed with error %" PRIu32 "", error);
+ return error;
+ }
+ break;
+ case EVENTID_DISMISS_HOVERING_CONTACT:
+ if ((error = read_dismiss_hovering_contact(context, s)))
+ {
+ WLog_ERR(TAG, "read_dismiss_hovering_contact failed with error %" PRIu32 "", error);
+ return error;
+ }
+ break;
+ case EVENTID_PEN:
+ if ((error = read_pen_event(context, s)))
+ {
+ WLog_ERR(TAG, "read_pen_event failed with error %" PRIu32 "", error);
+ return error;
+ }
+ break;
+ default:
+ WLog_ERR(TAG, "unexpected message type 0x%" PRIx16 "", priv->currentMsgType);
+ }
+
+ Stream_SetPosition(s, 0);
+ priv->waitingHeaders = TRUE;
+ priv->expectedBytes = RDPINPUT_HEADER_LENGTH;
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpei_server_send_sc_ready(RdpeiServerContext* context, UINT32 version, UINT32 features)
+{
+ ULONG written = 0;
+ RdpeiServerPrivate* priv = context->priv;
+ UINT32 pduLen = 4;
+
+ if (priv->automataState != STATE_INITIAL)
+ {
+ WLog_ERR(TAG, "called from unexpected state %d", priv->automataState);
+ return ERROR_INVALID_STATE;
+ }
+
+ Stream_SetPosition(priv->outputStream, 0);
+
+ if (version >= RDPINPUT_PROTOCOL_V300)
+ pduLen += 4;
+
+ if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH + pduLen))
+ {
+ WLog_ERR(TAG, "Stream_EnsureCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(priv->outputStream, EVENTID_SC_READY);
+ Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH + pduLen);
+ Stream_Write_UINT32(priv->outputStream, version);
+ if (version >= RDPINPUT_PROTOCOL_V300)
+ Stream_Write_UINT32(priv->outputStream, features);
+
+ if (!WTSVirtualChannelWrite(priv->channelHandle, (PCHAR)Stream_Buffer(priv->outputStream),
+ Stream_GetPosition(priv->outputStream), &written))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ priv->automataState = STATE_WAITING_CLIENT_READY;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpei_server_suspend(RdpeiServerContext* context)
+{
+ ULONG written = 0;
+ RdpeiServerPrivate* priv = context->priv;
+
+ switch (priv->automataState)
+ {
+ case STATE_SUSPENDED:
+ WLog_ERR(TAG, "already suspended");
+ return CHANNEL_RC_OK;
+ case STATE_WAITING_FRAME:
+ break;
+ default:
+ WLog_ERR(TAG, "called from unexpected state %d", priv->automataState);
+ return ERROR_INVALID_STATE;
+ }
+
+ Stream_SetPosition(priv->outputStream, 0);
+ if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH))
+ {
+ WLog_ERR(TAG, "Stream_EnsureCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(priv->outputStream, EVENTID_SUSPEND_TOUCH);
+ Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH);
+
+ if (!WTSVirtualChannelWrite(priv->channelHandle, (PCHAR)Stream_Buffer(priv->outputStream),
+ Stream_GetPosition(priv->outputStream), &written))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ priv->automataState = STATE_SUSPENDED;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpei_server_resume(RdpeiServerContext* context)
+{
+ ULONG written = 0;
+ RdpeiServerPrivate* priv = context->priv;
+
+ switch (priv->automataState)
+ {
+ case STATE_WAITING_FRAME:
+ WLog_ERR(TAG, "not suspended");
+ return CHANNEL_RC_OK;
+ case STATE_SUSPENDED:
+ break;
+ default:
+ WLog_ERR(TAG, "called from unexpected state %d", priv->automataState);
+ return ERROR_INVALID_STATE;
+ }
+
+ Stream_SetPosition(priv->outputStream, 0);
+ if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH))
+ {
+ WLog_ERR(TAG, "Stream_EnsureCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(priv->outputStream, EVENTID_RESUME_TOUCH);
+ Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH);
+
+ if (!WTSVirtualChannelWrite(priv->channelHandle, (PCHAR)Stream_Buffer(priv->outputStream),
+ Stream_GetPosition(priv->outputStream), &written))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ priv->automataState = STATE_WAITING_FRAME;
+ return CHANNEL_RC_OK;
+}
diff --git a/channels/rdpei/server/rdpei_main.h b/channels/rdpei/server/rdpei_main.h
new file mode 100644
index 0000000..cf3e3cb
--- /dev/null
+++ b/channels/rdpei/server/rdpei_main.h
@@ -0,0 +1,32 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Extended Input channel server-side implementation
+ *
+ * Copyright 2014 David Fort <contact@hardening-consulting.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPEI_SERVER_MAIN_H
+#define FREERDP_CHANNEL_RDPEI_SERVER_MAIN_H
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("rdpei.server")
+
+#endif /* FREERDP_CHANNEL_RDPEI_SERVER_MAIN_H */
diff --git a/channels/rdpemsc/CMakeLists.txt b/channels/rdpemsc/CMakeLists.txt
new file mode 100644
index 0000000..0b5f46b
--- /dev/null
+++ b/channels/rdpemsc/CMakeLists.txt
@@ -0,0 +1,22 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2023 Pascal Nowack <Pascal.Nowack@gmx.de>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("rdpemsc")
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/rdpemsc/ChannelOptions.cmake b/channels/rdpemsc/ChannelOptions.cmake
new file mode 100644
index 0000000..d89f37e
--- /dev/null
+++ b/channels/rdpemsc/ChannelOptions.cmake
@@ -0,0 +1,12 @@
+
+set(OPTION_DEFAULT ON)
+set(OPTION_CLIENT_DEFAULT OFF)
+set(OPTION_SERVER_DEFAULT ON)
+
+define_channel_options(NAME "rdpemsc" TYPE "dynamic"
+ DESCRIPTION "Mouse Cursor Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPEMSC]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/rdpemsc/server/CMakeLists.txt b/channels/rdpemsc/server/CMakeLists.txt
new file mode 100644
index 0000000..3384d70
--- /dev/null
+++ b/channels/rdpemsc/server/CMakeLists.txt
@@ -0,0 +1,28 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2023 Pascal Nowack <Pascal.Nowack@gmx.de>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_server("rdpemsc")
+
+set(${MODULE_PREFIX}_SRCS
+ mouse_cursor_main.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry")
diff --git a/channels/rdpemsc/server/mouse_cursor_main.c b/channels/rdpemsc/server/mouse_cursor_main.c
new file mode 100644
index 0000000..d7d2f78
--- /dev/null
+++ b/channels/rdpemsc/server/mouse_cursor_main.c
@@ -0,0 +1,727 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Mouse Cursor Virtual Channel Extension
+ *
+ * Copyright 2023 Pascal Nowack <Pascal.Nowack@gmx.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/server/rdpemsc.h>
+
+#define TAG CHANNELS_TAG("rdpemsc.server")
+
+typedef enum
+{
+ MOUSE_CURSOR_INITIAL,
+ MOUSE_CURSOR_OPENED,
+} eMouseCursorChannelState;
+
+typedef struct
+{
+ MouseCursorServerContext context;
+
+ HANDLE stopEvent;
+
+ HANDLE thread;
+ void* mouse_cursor_channel;
+
+ DWORD SessionId;
+
+ BOOL isOpened;
+ BOOL externalThread;
+
+ /* Channel state */
+ eMouseCursorChannelState state;
+
+ wStream* buffer;
+} mouse_cursor_server;
+
+static UINT mouse_cursor_server_initialize(MouseCursorServerContext* context, BOOL externalThread)
+{
+ UINT error = CHANNEL_RC_OK;
+ mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context;
+
+ WINPR_ASSERT(mouse_cursor);
+
+ if (mouse_cursor->isOpened)
+ {
+ WLog_WARN(TAG, "Application error: Mouse Cursor channel already initialized, "
+ "calling in this state is not possible!");
+ return ERROR_INVALID_STATE;
+ }
+
+ mouse_cursor->externalThread = externalThread;
+
+ return error;
+}
+
+static UINT mouse_cursor_server_open_channel(mouse_cursor_server* mouse_cursor)
+{
+ MouseCursorServerContext* context = NULL;
+ DWORD Error = ERROR_SUCCESS;
+ DWORD BytesReturned = 0;
+ PULONG pSessionId = NULL;
+ UINT32 channelId = 0;
+ BOOL status = TRUE;
+
+ WINPR_ASSERT(mouse_cursor);
+ context = &mouse_cursor->context;
+ WINPR_ASSERT(context);
+
+ if (WTSQuerySessionInformationA(mouse_cursor->context.vcm, WTS_CURRENT_SESSION, WTSSessionId,
+ (LPSTR*)&pSessionId, &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSQuerySessionInformationA failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ mouse_cursor->SessionId = (DWORD)*pSessionId;
+ WTSFreeMemory(pSessionId);
+
+ mouse_cursor->mouse_cursor_channel = WTSVirtualChannelOpenEx(
+ mouse_cursor->SessionId, RDPEMSC_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC);
+ if (!mouse_cursor->mouse_cursor_channel)
+ {
+ Error = GetLastError();
+ WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", Error);
+ return Error;
+ }
+
+ channelId = WTSChannelGetIdByHandle(mouse_cursor->mouse_cursor_channel);
+
+ IFCALLRET(context->ChannelIdAssigned, status, context, channelId);
+ if (!status)
+ {
+ WLog_ERR(TAG, "context->ChannelIdAssigned failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return Error;
+}
+
+static BOOL read_cap_set(wStream* s, wArrayList* capsSets)
+{
+ RDP_MOUSE_CURSOR_CAPSET* capsSet = NULL;
+ UINT32 signature = 0;
+ RDP_MOUSE_CURSOR_CAPVERSION version = RDP_MOUSE_CURSOR_CAPVERSION_INVALID;
+ UINT32 size = 0;
+ size_t capsDataSize = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return FALSE;
+
+ Stream_Read_UINT32(s, signature);
+ Stream_Read_UINT32(s, version);
+ Stream_Read_UINT32(s, size);
+
+ if (size < 12)
+ {
+ WLog_ERR(TAG, "Size of caps set is invalid: %u", size);
+ return FALSE;
+ }
+
+ capsDataSize = size - 12;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, capsDataSize))
+ return FALSE;
+
+ switch (version)
+ {
+ case RDP_MOUSE_CURSOR_CAPVERSION_1:
+ {
+ RDP_MOUSE_CURSOR_CAPSET_VERSION1* capsSetV1 = NULL;
+
+ capsSetV1 = calloc(1, sizeof(RDP_MOUSE_CURSOR_CAPSET_VERSION1));
+ if (!capsSetV1)
+ return FALSE;
+
+ capsSet = (RDP_MOUSE_CURSOR_CAPSET*)capsSetV1;
+ break;
+ }
+ default:
+ WLog_WARN(TAG, "Received caps set with unknown version %u", version);
+ Stream_Seek(s, capsDataSize);
+ return TRUE;
+ }
+ WINPR_ASSERT(capsSet);
+
+ capsSet->signature = signature;
+ capsSet->version = version;
+ capsSet->size = size;
+
+ if (!ArrayList_Append(capsSets, capsSet))
+ {
+ WLog_ERR(TAG, "Failed to append caps set to arraylist");
+ free(capsSet);
+ return FALSE;
+ }
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): ArrayList_Append owns capsSet
+ return TRUE;
+}
+
+static UINT mouse_cursor_server_recv_cs_caps_advertise(MouseCursorServerContext* context,
+ wStream* s,
+ const RDP_MOUSE_CURSOR_HEADER* header)
+{
+ RDP_MOUSE_CURSOR_CAPS_ADVERTISE_PDU pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ pdu.header = *header;
+
+ /* There must be at least one capability set present */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return ERROR_NO_DATA;
+
+ pdu.capsSets = ArrayList_New(FALSE);
+ if (!pdu.capsSets)
+ {
+ WLog_ERR(TAG, "Failed to allocate arraylist");
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ wObject* aobj = ArrayList_Object(pdu.capsSets);
+ WINPR_ASSERT(aobj);
+ aobj->fnObjectFree = free;
+
+ while (Stream_GetRemainingLength(s) > 0)
+ {
+ if (!read_cap_set(s, pdu.capsSets))
+ {
+ ArrayList_Free(pdu.capsSets);
+ return ERROR_INVALID_DATA;
+ }
+ }
+
+ IFCALLRET(context->CapsAdvertise, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->CapsAdvertise failed with error %" PRIu32 "", error);
+
+ ArrayList_Free(pdu.capsSets);
+
+ return error;
+}
+
+static UINT mouse_cursor_process_message(mouse_cursor_server* mouse_cursor)
+{
+ BOOL rc = 0;
+ UINT error = ERROR_INTERNAL_ERROR;
+ ULONG BytesReturned = 0;
+ RDP_MOUSE_CURSOR_HEADER header = { 0 };
+ wStream* s = NULL;
+
+ WINPR_ASSERT(mouse_cursor);
+ WINPR_ASSERT(mouse_cursor->mouse_cursor_channel);
+
+ s = mouse_cursor->buffer;
+ WINPR_ASSERT(s);
+
+ Stream_SetPosition(s, 0);
+ rc = WTSVirtualChannelRead(mouse_cursor->mouse_cursor_channel, 0, NULL, 0, &BytesReturned);
+ if (!rc)
+ goto out;
+
+ if (BytesReturned < 1)
+ {
+ error = CHANNEL_RC_OK;
+ goto out;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ if (WTSVirtualChannelRead(mouse_cursor->mouse_cursor_channel, 0, (PCHAR)Stream_Buffer(s),
+ (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ goto out;
+ }
+
+ Stream_SetLength(s, BytesReturned);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RDPEMSC_HEADER_SIZE))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT8(s, header.pduType);
+ Stream_Read_UINT8(s, header.updateType);
+ Stream_Read_UINT16(s, header.reserved);
+
+ switch (header.pduType)
+ {
+ case PDUTYPE_CS_CAPS_ADVERTISE:
+ error = mouse_cursor_server_recv_cs_caps_advertise(&mouse_cursor->context, s, &header);
+ break;
+ default:
+ WLog_ERR(TAG, "mouse_cursor_process_message: unknown or invalid pduType %" PRIu8 "",
+ header.pduType);
+ break;
+ }
+
+out:
+ if (error)
+ WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+static UINT mouse_cursor_server_context_poll_int(MouseCursorServerContext* context)
+{
+ mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context;
+ UINT error = ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(mouse_cursor);
+
+ switch (mouse_cursor->state)
+ {
+ case MOUSE_CURSOR_INITIAL:
+ error = mouse_cursor_server_open_channel(mouse_cursor);
+ if (error)
+ WLog_ERR(TAG, "mouse_cursor_server_open_channel failed with error %" PRIu32 "!",
+ error);
+ else
+ mouse_cursor->state = MOUSE_CURSOR_OPENED;
+ break;
+ case MOUSE_CURSOR_OPENED:
+ error = mouse_cursor_process_message(mouse_cursor);
+ break;
+ }
+
+ return error;
+}
+
+static HANDLE mouse_cursor_server_get_channel_handle(mouse_cursor_server* mouse_cursor)
+{
+ void* buffer = NULL;
+ DWORD BytesReturned = 0;
+ HANDLE ChannelEvent = NULL;
+
+ WINPR_ASSERT(mouse_cursor);
+
+ if (WTSVirtualChannelQuery(mouse_cursor->mouse_cursor_channel, WTSVirtualEventHandle, &buffer,
+ &BytesReturned) == TRUE)
+ {
+ if (BytesReturned == sizeof(HANDLE))
+ CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE));
+
+ WTSFreeMemory(buffer);
+ }
+
+ return ChannelEvent;
+}
+
+static DWORD WINAPI mouse_cursor_server_thread_func(LPVOID arg)
+{
+ DWORD nCount = 0;
+ HANDLE events[2] = { 0 };
+ mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)arg;
+ UINT error = CHANNEL_RC_OK;
+ DWORD status = 0;
+
+ WINPR_ASSERT(mouse_cursor);
+
+ nCount = 0;
+ events[nCount++] = mouse_cursor->stopEvent;
+
+ while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0))
+ {
+ switch (mouse_cursor->state)
+ {
+ case MOUSE_CURSOR_INITIAL:
+ error = mouse_cursor_server_context_poll_int(&mouse_cursor->context);
+ if (error == CHANNEL_RC_OK)
+ {
+ events[1] = mouse_cursor_server_get_channel_handle(mouse_cursor);
+ nCount = 2;
+ }
+ break;
+ case MOUSE_CURSOR_OPENED:
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+ switch (status)
+ {
+ case WAIT_OBJECT_0:
+ break;
+ case WAIT_OBJECT_0 + 1:
+ case WAIT_TIMEOUT:
+ error = mouse_cursor_server_context_poll_int(&mouse_cursor->context);
+ break;
+
+ case WAIT_FAILED:
+ default:
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+ break;
+ }
+ }
+
+ WTSVirtualChannelClose(mouse_cursor->mouse_cursor_channel);
+ mouse_cursor->mouse_cursor_channel = NULL;
+
+ if (error && mouse_cursor->context.rdpcontext)
+ setChannelError(mouse_cursor->context.rdpcontext, error,
+ "mouse_cursor_server_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+static UINT mouse_cursor_server_open(MouseCursorServerContext* context)
+{
+ mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context;
+
+ WINPR_ASSERT(mouse_cursor);
+
+ if (!mouse_cursor->externalThread && (mouse_cursor->thread == NULL))
+ {
+ mouse_cursor->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!mouse_cursor->stopEvent)
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ mouse_cursor->thread =
+ CreateThread(NULL, 0, mouse_cursor_server_thread_func, mouse_cursor, 0, NULL);
+ if (!mouse_cursor->thread)
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ CloseHandle(mouse_cursor->stopEvent);
+ mouse_cursor->stopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+ mouse_cursor->isOpened = TRUE;
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT mouse_cursor_server_close(MouseCursorServerContext* context)
+{
+ UINT error = CHANNEL_RC_OK;
+ mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context;
+
+ WINPR_ASSERT(mouse_cursor);
+
+ if (!mouse_cursor->externalThread && mouse_cursor->thread)
+ {
+ SetEvent(mouse_cursor->stopEvent);
+
+ if (WaitForSingleObject(mouse_cursor->thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ CloseHandle(mouse_cursor->thread);
+ CloseHandle(mouse_cursor->stopEvent);
+ mouse_cursor->thread = NULL;
+ mouse_cursor->stopEvent = NULL;
+ }
+ if (mouse_cursor->externalThread)
+ {
+ if (mouse_cursor->state != MOUSE_CURSOR_INITIAL)
+ {
+ WTSVirtualChannelClose(mouse_cursor->mouse_cursor_channel);
+ mouse_cursor->mouse_cursor_channel = NULL;
+ mouse_cursor->state = MOUSE_CURSOR_INITIAL;
+ }
+ }
+ mouse_cursor->isOpened = FALSE;
+
+ return error;
+}
+
+static UINT mouse_cursor_server_context_poll(MouseCursorServerContext* context)
+{
+ mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context;
+
+ WINPR_ASSERT(mouse_cursor);
+
+ if (!mouse_cursor->externalThread)
+ return ERROR_INTERNAL_ERROR;
+
+ return mouse_cursor_server_context_poll_int(context);
+}
+
+static BOOL mouse_cursor_server_context_handle(MouseCursorServerContext* context, HANDLE* handle)
+{
+ mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context;
+
+ WINPR_ASSERT(mouse_cursor);
+ WINPR_ASSERT(handle);
+
+ if (!mouse_cursor->externalThread)
+ return FALSE;
+ if (mouse_cursor->state == MOUSE_CURSOR_INITIAL)
+ return FALSE;
+
+ *handle = mouse_cursor_server_get_channel_handle(mouse_cursor);
+
+ return TRUE;
+}
+
+static wStream* mouse_cursor_server_packet_new(size_t size, RDP_MOUSE_CURSOR_PDUTYPE pduType,
+ const RDP_MOUSE_CURSOR_HEADER* header)
+{
+ wStream* s = NULL;
+
+ /* Allocate what we need plus header bytes */
+ s = Stream_New(NULL, size + RDPEMSC_HEADER_SIZE);
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return NULL;
+ }
+
+ Stream_Write_UINT8(s, pduType);
+ Stream_Write_UINT8(s, header->updateType);
+ Stream_Write_UINT16(s, header->reserved);
+
+ return s;
+}
+
+static UINT mouse_cursor_server_packet_send(MouseCursorServerContext* context, wStream* s)
+{
+ mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context;
+ UINT error = CHANNEL_RC_OK;
+ ULONG written = 0;
+
+ WINPR_ASSERT(mouse_cursor);
+ WINPR_ASSERT(s);
+
+ if (!WTSVirtualChannelWrite(mouse_cursor->mouse_cursor_channel, (PCHAR)Stream_Buffer(s),
+ Stream_GetPosition(s), &written))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ if (written < Stream_GetPosition(s))
+ {
+ WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written,
+ Stream_GetPosition(s));
+ }
+
+out:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+static UINT
+mouse_cursor_server_send_sc_caps_confirm(MouseCursorServerContext* context,
+ const RDP_MOUSE_CURSOR_CAPS_CONFIRM_PDU* capsConfirm)
+{
+ RDP_MOUSE_CURSOR_CAPSET* capsetHeader = NULL;
+ RDP_MOUSE_CURSOR_PDUTYPE pduType = PDUTYPE_EMSC_RESERVED;
+ size_t caps_size = 0;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(capsConfirm);
+
+ capsetHeader = capsConfirm->capsSet;
+ WINPR_ASSERT(capsetHeader);
+
+ caps_size = 12;
+ switch (capsetHeader->version)
+ {
+ case RDP_MOUSE_CURSOR_CAPVERSION_1:
+ break;
+ default:
+ WINPR_ASSERT(FALSE);
+ break;
+ }
+
+ pduType = PDUTYPE_SC_CAPS_CONFIRM;
+ s = mouse_cursor_server_packet_new(caps_size, pduType, &capsConfirm->header);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT32(s, capsetHeader->signature);
+ Stream_Write_UINT32(s, capsetHeader->version);
+ Stream_Write_UINT32(s, capsetHeader->size);
+
+ /* Write capsData */
+ switch (capsetHeader->version)
+ {
+ case RDP_MOUSE_CURSOR_CAPVERSION_1:
+ break;
+ default:
+ WINPR_ASSERT(FALSE);
+ break;
+ }
+
+ return mouse_cursor_server_packet_send(context, s);
+}
+
+static void write_point16(wStream* s, const TS_POINT16* point16)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(point16);
+
+ Stream_Write_UINT16(s, point16->xPos);
+ Stream_Write_UINT16(s, point16->yPos);
+}
+
+static UINT mouse_cursor_server_send_sc_mouseptr_update(
+ MouseCursorServerContext* context, const RDP_MOUSE_CURSOR_MOUSEPTR_UPDATE_PDU* mouseptrUpdate)
+{
+ TS_POINT16* position = NULL;
+ TS_POINTERATTRIBUTE* pointerAttribute = NULL;
+ TS_LARGEPOINTERATTRIBUTE* largePointerAttribute = NULL;
+ RDP_MOUSE_CURSOR_PDUTYPE pduType = PDUTYPE_EMSC_RESERVED;
+ size_t update_size = 0;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(mouseptrUpdate);
+
+ position = mouseptrUpdate->position;
+ pointerAttribute = mouseptrUpdate->pointerAttribute;
+ largePointerAttribute = mouseptrUpdate->largePointerAttribute;
+
+ switch (mouseptrUpdate->header.updateType)
+ {
+ case TS_UPDATETYPE_MOUSEPTR_SYSTEM_NULL:
+ case TS_UPDATETYPE_MOUSEPTR_SYSTEM_DEFAULT:
+ update_size = 0;
+ break;
+ case TS_UPDATETYPE_MOUSEPTR_POSITION:
+ WINPR_ASSERT(position);
+ update_size = 4;
+ break;
+ case TS_UPDATETYPE_MOUSEPTR_CACHED:
+ WINPR_ASSERT(mouseptrUpdate->cachedPointerIndex);
+ update_size = 2;
+ break;
+ case TS_UPDATETYPE_MOUSEPTR_POINTER:
+ WINPR_ASSERT(pointerAttribute);
+ update_size = 2 + 2 + 4 + 2 + 2 + 2 + 2;
+ update_size += pointerAttribute->lengthAndMask;
+ update_size += pointerAttribute->lengthXorMask;
+ break;
+ case TS_UPDATETYPE_MOUSEPTR_LARGE_POINTER:
+ WINPR_ASSERT(largePointerAttribute);
+ update_size = 2 + 2 + 4 + 2 + 2 + 4 + 4;
+ update_size += largePointerAttribute->lengthAndMask;
+ update_size += largePointerAttribute->lengthXorMask;
+ break;
+ default:
+ WINPR_ASSERT(FALSE);
+ break;
+ }
+
+ pduType = PDUTYPE_SC_MOUSEPTR_UPDATE;
+ s = mouse_cursor_server_packet_new(update_size, pduType, &mouseptrUpdate->header);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ switch (mouseptrUpdate->header.updateType)
+ {
+ case TS_UPDATETYPE_MOUSEPTR_SYSTEM_NULL:
+ case TS_UPDATETYPE_MOUSEPTR_SYSTEM_DEFAULT:
+ break;
+ case TS_UPDATETYPE_MOUSEPTR_POSITION:
+ write_point16(s, position);
+ break;
+ case TS_UPDATETYPE_MOUSEPTR_CACHED:
+ Stream_Write_UINT16(s, *mouseptrUpdate->cachedPointerIndex);
+ break;
+ case TS_UPDATETYPE_MOUSEPTR_POINTER:
+ Stream_Write_UINT16(s, pointerAttribute->xorBpp);
+ Stream_Write_UINT16(s, pointerAttribute->cacheIndex);
+ write_point16(s, &pointerAttribute->hotSpot);
+ Stream_Write_UINT16(s, pointerAttribute->width);
+ Stream_Write_UINT16(s, pointerAttribute->height);
+ Stream_Write_UINT16(s, pointerAttribute->lengthAndMask);
+ Stream_Write_UINT16(s, pointerAttribute->lengthXorMask);
+ Stream_Write(s, pointerAttribute->xorMaskData, pointerAttribute->lengthXorMask);
+ Stream_Write(s, pointerAttribute->andMaskData, pointerAttribute->lengthAndMask);
+ break;
+ case TS_UPDATETYPE_MOUSEPTR_LARGE_POINTER:
+ Stream_Write_UINT16(s, largePointerAttribute->xorBpp);
+ Stream_Write_UINT16(s, largePointerAttribute->cacheIndex);
+ write_point16(s, &largePointerAttribute->hotSpot);
+ Stream_Write_UINT16(s, largePointerAttribute->width);
+ Stream_Write_UINT16(s, largePointerAttribute->height);
+ Stream_Write_UINT32(s, largePointerAttribute->lengthAndMask);
+ Stream_Write_UINT32(s, largePointerAttribute->lengthXorMask);
+ Stream_Write(s, largePointerAttribute->xorMaskData,
+ largePointerAttribute->lengthXorMask);
+ Stream_Write(s, largePointerAttribute->andMaskData,
+ largePointerAttribute->lengthAndMask);
+ break;
+ default:
+ WINPR_ASSERT(FALSE);
+ break;
+ }
+
+ return mouse_cursor_server_packet_send(context, s);
+}
+
+MouseCursorServerContext* mouse_cursor_server_context_new(HANDLE vcm)
+{
+ mouse_cursor_server* mouse_cursor =
+ (mouse_cursor_server*)calloc(1, sizeof(mouse_cursor_server));
+
+ if (!mouse_cursor)
+ return NULL;
+
+ mouse_cursor->context.vcm = vcm;
+ mouse_cursor->context.Initialize = mouse_cursor_server_initialize;
+ mouse_cursor->context.Open = mouse_cursor_server_open;
+ mouse_cursor->context.Close = mouse_cursor_server_close;
+ mouse_cursor->context.Poll = mouse_cursor_server_context_poll;
+ mouse_cursor->context.ChannelHandle = mouse_cursor_server_context_handle;
+
+ mouse_cursor->context.CapsConfirm = mouse_cursor_server_send_sc_caps_confirm;
+ mouse_cursor->context.MouseptrUpdate = mouse_cursor_server_send_sc_mouseptr_update;
+
+ mouse_cursor->buffer = Stream_New(NULL, 4096);
+ if (!mouse_cursor->buffer)
+ goto fail;
+
+ return &mouse_cursor->context;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ mouse_cursor_server_context_free(&mouse_cursor->context);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void mouse_cursor_server_context_free(MouseCursorServerContext* context)
+{
+ mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context;
+
+ if (mouse_cursor)
+ {
+ mouse_cursor_server_close(context);
+ Stream_Free(mouse_cursor->buffer, TRUE);
+ }
+
+ free(mouse_cursor);
+}
diff --git a/channels/rdpgfx/CMakeLists.txt b/channels/rdpgfx/CMakeLists.txt
new file mode 100644
index 0000000..04820de
--- /dev/null
+++ b/channels/rdpgfx/CMakeLists.txt
@@ -0,0 +1,26 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("rdpgfx")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/rdpgfx/ChannelOptions.cmake b/channels/rdpgfx/ChannelOptions.cmake
new file mode 100644
index 0000000..acb8de8
--- /dev/null
+++ b/channels/rdpgfx/ChannelOptions.cmake
@@ -0,0 +1,13 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT OFF)
+
+define_channel_options(NAME "rdpgfx" TYPE "dynamic"
+ DESCRIPTION "Graphics Pipeline Extension"
+ SPECIFICATIONS "[MS-RDPEGFX]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/rdpgfx/client/CMakeLists.txt b/channels/rdpgfx/client/CMakeLists.txt
new file mode 100644
index 0000000..3338f98
--- /dev/null
+++ b/channels/rdpgfx/client/CMakeLists.txt
@@ -0,0 +1,35 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("rdpgfx")
+
+set(${MODULE_PREFIX}_SRCS
+ rdpgfx_main.c
+ rdpgfx_main.h
+ rdpgfx_codec.c
+ rdpgfx_codec.h
+ ../rdpgfx_common.c
+ ../rdpgfx_common.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr freerdp
+)
+include_directories(..)
+
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
+
diff --git a/channels/rdpgfx/client/rdpgfx_codec.c b/channels/rdpgfx/client/rdpgfx_codec.c
new file mode 100644
index 0000000..488e357
--- /dev/null
+++ b/channels/rdpgfx/client/rdpgfx_codec.c
@@ -0,0 +1,294 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Graphics Pipeline Extension
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+#include <freerdp/log.h>
+#include <freerdp/utils/profiler.h>
+
+#include "rdpgfx_common.h"
+
+#include "rdpgfx_codec.h"
+
+#define TAG CHANNELS_TAG("rdpgfx.client")
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_read_h264_metablock(RDPGFX_PLUGIN* gfx, wStream* s, RDPGFX_H264_METABLOCK* meta)
+{
+ RECTANGLE_16* regionRect = NULL;
+ RDPGFX_H264_QUANT_QUALITY* quantQualityVal = NULL;
+ UINT error = ERROR_INVALID_DATA;
+ meta->regionRects = NULL;
+ meta->quantQualityVals = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ goto error_out;
+
+ Stream_Read_UINT32(s, meta->numRegionRects); /* numRegionRects (4 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, meta->numRegionRects, 8ull))
+ goto error_out;
+
+ meta->regionRects = (RECTANGLE_16*)calloc(meta->numRegionRects, sizeof(RECTANGLE_16));
+
+ if (!meta->regionRects)
+ {
+ WLog_ERR(TAG, "malloc failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+
+ meta->quantQualityVals =
+ (RDPGFX_H264_QUANT_QUALITY*)calloc(meta->numRegionRects, sizeof(RDPGFX_H264_QUANT_QUALITY));
+
+ if (!meta->quantQualityVals)
+ {
+ WLog_ERR(TAG, "malloc failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+
+ WLog_DBG(TAG, "H264_METABLOCK: numRegionRects: %" PRIu32 "", meta->numRegionRects);
+
+ for (UINT32 index = 0; index < meta->numRegionRects; index++)
+ {
+ regionRect = &(meta->regionRects[index]);
+
+ if ((error = rdpgfx_read_rect16(s, regionRect)))
+ {
+ WLog_ERR(TAG, "rdpgfx_read_rect16 failed with error %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ WLog_DBG(TAG,
+ "regionRects[%" PRIu32 "]: left: %" PRIu16 " top: %" PRIu16 " right: %" PRIu16
+ " bottom: %" PRIu16 "",
+ index, regionRect->left, regionRect->top, regionRect->right, regionRect->bottom);
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, meta->numRegionRects, 2ull))
+ {
+ error = ERROR_INVALID_DATA;
+ goto error_out;
+ }
+
+ for (UINT32 index = 0; index < meta->numRegionRects; index++)
+ {
+ quantQualityVal = &(meta->quantQualityVals[index]);
+ Stream_Read_UINT8(s, quantQualityVal->qpVal); /* qpVal (1 byte) */
+ Stream_Read_UINT8(s, quantQualityVal->qualityVal); /* qualityVal (1 byte) */
+ quantQualityVal->qp = quantQualityVal->qpVal & 0x3F;
+ quantQualityVal->r = (quantQualityVal->qpVal >> 6) & 1;
+ quantQualityVal->p = (quantQualityVal->qpVal >> 7) & 1;
+ WLog_DBG(TAG,
+ "quantQualityVals[%" PRIu32 "]: qp: %" PRIu8 " r: %" PRIu8 " p: %" PRIu8
+ " qualityVal: %" PRIu8 "",
+ index, quantQualityVal->qp, quantQualityVal->r, quantQualityVal->p,
+ quantQualityVal->qualityVal);
+ }
+
+ return CHANNEL_RC_OK;
+error_out:
+ free_h264_metablock(meta);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_decode_AVC420(RDPGFX_PLUGIN* gfx, RDPGFX_SURFACE_COMMAND* cmd)
+{
+ UINT error = 0;
+ wStream* s = NULL;
+ RDPGFX_AVC420_BITMAP_STREAM h264;
+ RdpgfxClientContext* context = gfx->context;
+ s = Stream_New(cmd->data, cmd->length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if ((error = rdpgfx_read_h264_metablock(gfx, s, &(h264.meta))))
+ {
+ Stream_Free(s, FALSE);
+ WLog_ERR(TAG, "rdpgfx_read_h264_metablock failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ h264.data = Stream_Pointer(s);
+ h264.length = (UINT32)Stream_GetRemainingLength(s);
+ Stream_Free(s, FALSE);
+ cmd->extra = (void*)&h264;
+
+ if (context)
+ {
+ IFCALLRET(context->SurfaceCommand, error, context, cmd);
+
+ if (error)
+ WLog_ERR(TAG, "context->SurfaceCommand failed with error %" PRIu32 "", error);
+ }
+
+ free_h264_metablock(&h264.meta);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_decode_AVC444(RDPGFX_PLUGIN* gfx, RDPGFX_SURFACE_COMMAND* cmd)
+{
+ UINT error = 0;
+ UINT32 tmp = 0;
+ size_t pos1 = 0;
+ size_t pos2 = 0;
+
+ RDPGFX_AVC444_BITMAP_STREAM h264 = { 0 };
+ RdpgfxClientContext* context = gfx->context;
+ wStream* s = Stream_New(cmd->data, cmd->length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ {
+ error = ERROR_INVALID_DATA;
+ goto fail;
+ }
+
+ Stream_Read_UINT32(s, tmp);
+ h264.cbAvc420EncodedBitstream1 = tmp & 0x3FFFFFFFUL;
+ h264.LC = (tmp >> 30UL) & 0x03UL;
+
+ if (h264.LC == 0x03)
+ {
+ error = ERROR_INVALID_DATA;
+ goto fail;
+ }
+
+ pos1 = Stream_GetPosition(s);
+
+ if ((error = rdpgfx_read_h264_metablock(gfx, s, &(h264.bitstream[0].meta))))
+ {
+ WLog_ERR(TAG, "rdpgfx_read_h264_metablock failed with error %" PRIu32 "!", error);
+ goto fail;
+ }
+
+ pos2 = Stream_GetPosition(s);
+ h264.bitstream[0].data = Stream_Pointer(s);
+
+ if (h264.LC == 0)
+ {
+ tmp = h264.cbAvc420EncodedBitstream1 - pos2 + pos1;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, tmp))
+ {
+ error = ERROR_INVALID_DATA;
+ goto fail;
+ }
+
+ h264.bitstream[0].length = tmp;
+ Stream_Seek(s, tmp);
+
+ if ((error = rdpgfx_read_h264_metablock(gfx, s, &(h264.bitstream[1].meta))))
+ {
+ WLog_ERR(TAG, "rdpgfx_read_h264_metablock failed with error %" PRIu32 "!", error);
+ goto fail;
+ }
+
+ h264.bitstream[1].data = Stream_Pointer(s);
+ h264.bitstream[1].length = Stream_GetRemainingLength(s);
+ }
+ else
+ h264.bitstream[0].length = Stream_GetRemainingLength(s);
+
+ cmd->extra = (void*)&h264;
+
+ if (context)
+ {
+ IFCALLRET(context->SurfaceCommand, error, context, cmd);
+
+ if (error)
+ WLog_ERR(TAG, "context->SurfaceCommand failed with error %" PRIu32 "", error);
+ }
+
+fail:
+ Stream_Free(s, FALSE);
+ free_h264_metablock(&h264.bitstream[0].meta);
+ free_h264_metablock(&h264.bitstream[1].meta);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpgfx_decode(RDPGFX_PLUGIN* gfx, RDPGFX_SURFACE_COMMAND* cmd)
+{
+ UINT error = CHANNEL_RC_OK;
+ RdpgfxClientContext* context = gfx->context;
+ PROFILER_ENTER(context->SurfaceProfiler)
+
+ switch (cmd->codecId)
+ {
+ case RDPGFX_CODECID_AVC420:
+ if ((error = rdpgfx_decode_AVC420(gfx, cmd)))
+ WLog_ERR(TAG, "rdpgfx_decode_AVC420 failed with error %" PRIu32 "", error);
+
+ break;
+
+ case RDPGFX_CODECID_AVC444:
+ case RDPGFX_CODECID_AVC444v2:
+ if ((error = rdpgfx_decode_AVC444(gfx, cmd)))
+ WLog_ERR(TAG, "rdpgfx_decode_AVC444 failed with error %" PRIu32 "", error);
+
+ break;
+
+ default:
+ if (context)
+ {
+ IFCALLRET(context->SurfaceCommand, error, context, cmd);
+
+ if (error)
+ WLog_ERR(TAG, "context->SurfaceCommand failed with error %" PRIu32 "", error);
+ }
+
+ break;
+ }
+
+ PROFILER_EXIT(context->SurfaceProfiler)
+ return error;
+}
diff --git a/channels/rdpgfx/client/rdpgfx_codec.h b/channels/rdpgfx/client/rdpgfx_codec.h
new file mode 100644
index 0000000..03d1bac
--- /dev/null
+++ b/channels/rdpgfx/client/rdpgfx_codec.h
@@ -0,0 +1,35 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Graphics Pipeline Extension
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPGFX_CLIENT_CODEC_H
+#define FREERDP_CHANNEL_RDPGFX_CLIENT_CODEC_H
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+
+#include <freerdp/channels/rdpgfx.h>
+#include <freerdp/api.h>
+
+#include "rdpgfx_main.h"
+
+FREERDP_LOCAL UINT rdpgfx_decode(RDPGFX_PLUGIN* gfx, RDPGFX_SURFACE_COMMAND* cmd);
+
+#endif /* FREERDP_CHANNEL_RDPGFX_CLIENT_CODEC_H */
diff --git a/channels/rdpgfx/client/rdpgfx_main.c b/channels/rdpgfx/client/rdpgfx_main.c
new file mode 100644
index 0000000..dd59c8b
--- /dev/null
+++ b/channels/rdpgfx/client/rdpgfx_main.c
@@ -0,0 +1,2417 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Graphics Pipeline Extension
+ *
+ * Copyright 2013-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 Thincast Technologies GmbH
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+#include <winpr/print.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/sysinfo.h>
+#include <winpr/cmdline.h>
+#include <winpr/collections.h>
+
+#include <freerdp/addin.h>
+#include <freerdp/channels/log.h>
+
+#include "rdpgfx_common.h"
+#include "rdpgfx_codec.h"
+
+#include "rdpgfx_main.h"
+
+#define TAG CHANNELS_TAG("rdpgfx.client")
+
+static BOOL delete_surface(const void* key, void* value, void* arg)
+{
+ const UINT16 id = (UINT16)(uintptr_t)(key);
+ RdpgfxClientContext* context = arg;
+ RDPGFX_DELETE_SURFACE_PDU pdu = { 0 };
+
+ WINPR_UNUSED(value);
+ pdu.surfaceId = id - 1;
+
+ if (context)
+ {
+ UINT error = CHANNEL_RC_OK;
+ IFCALLRET(context->DeleteSurface, error, context, &pdu);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "context->DeleteSurface failed with error %" PRIu32 "", error);
+ }
+ }
+ return TRUE;
+}
+
+static void free_surfaces(RdpgfxClientContext* context, wHashTable* SurfaceTable)
+{
+ HashTable_Foreach(SurfaceTable, delete_surface, context);
+}
+
+static void evict_cache_slots(RdpgfxClientContext* context, UINT16 MaxCacheSlots, void** CacheSlots)
+{
+ WINPR_ASSERT(CacheSlots);
+ for (UINT16 index = 0; index < MaxCacheSlots; index++)
+ {
+ if (CacheSlots[index])
+ {
+ RDPGFX_EVICT_CACHE_ENTRY_PDU pdu = { 0 };
+ pdu.cacheSlot = (UINT16)index + 1;
+
+ if (context && context->EvictCacheEntry)
+ {
+ context->EvictCacheEntry(context, &pdu);
+ }
+
+ CacheSlots[index] = NULL;
+ }
+ }
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_caps_advertise_pdu(RdpgfxClientContext* context,
+ const RDPGFX_CAPS_ADVERTISE_PDU* pdu)
+{
+ UINT error = CHANNEL_RC_OK;
+ RDPGFX_HEADER header = { 0 };
+ RDPGFX_PLUGIN* gfx = NULL;
+ GENERIC_CHANNEL_CALLBACK* callback = NULL;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(pdu);
+ WINPR_ASSERT(context);
+
+ gfx = (RDPGFX_PLUGIN*)context->handle;
+
+ if (!gfx || !gfx->base.listener_callback)
+ return ERROR_BAD_ARGUMENTS;
+
+ callback = gfx->base.listener_callback->channel_callback;
+
+ header.flags = 0;
+ header.cmdId = RDPGFX_CMDID_CAPSADVERTISE;
+ header.pduLength = RDPGFX_HEADER_SIZE + 2;
+
+ for (UINT16 index = 0; index < pdu->capsSetCount; index++)
+ {
+ const RDPGFX_CAPSET* capsSet = &(pdu->capsSets[index]);
+ header.pduLength += RDPGFX_CAPSET_BASE_SIZE + capsSet->length;
+ }
+
+ DEBUG_RDPGFX(gfx->log, "SendCapsAdvertisePdu %" PRIu16 "", pdu->capsSetCount);
+ s = Stream_New(NULL, header.pduLength);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if ((error = rdpgfx_write_header(s, &header)))
+ goto fail;
+
+ /* RDPGFX_CAPS_ADVERTISE_PDU */
+ Stream_Write_UINT16(s, pdu->capsSetCount); /* capsSetCount (2 bytes) */
+
+ for (UINT16 index = 0; index < pdu->capsSetCount; index++)
+ {
+ const RDPGFX_CAPSET* capsSet = &(pdu->capsSets[index]);
+ Stream_Write_UINT32(s, capsSet->version); /* version (4 bytes) */
+ Stream_Write_UINT32(s, capsSet->length); /* capsDataLength (4 bytes) */
+ Stream_Write_UINT32(s, capsSet->flags); /* capsData (4 bytes) */
+ Stream_Zero(s, capsSet->length - 4);
+ }
+
+ Stream_SealLength(s);
+ error = callback->channel->Write(callback->channel, (UINT32)Stream_Length(s), Stream_Buffer(s),
+ NULL);
+fail:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+static BOOL rdpgfx_is_capability_filtered(RDPGFX_PLUGIN* gfx, UINT32 caps)
+{
+ WINPR_ASSERT(gfx);
+ const UINT32 filter =
+ freerdp_settings_get_uint32(gfx->rdpcontext->settings, FreeRDP_GfxCapsFilter);
+ const UINT32 capList[] = { RDPGFX_CAPVERSION_8, RDPGFX_CAPVERSION_81,
+ RDPGFX_CAPVERSION_10, RDPGFX_CAPVERSION_101,
+ RDPGFX_CAPVERSION_102, RDPGFX_CAPVERSION_103,
+ RDPGFX_CAPVERSION_104, RDPGFX_CAPVERSION_105,
+ RDPGFX_CAPVERSION_106, RDPGFX_CAPVERSION_106_ERR,
+ RDPGFX_CAPVERSION_107 };
+
+ for (size_t x = 0; x < ARRAYSIZE(capList); x++)
+ {
+ if (caps == capList[x])
+ return (filter & (1 << x)) != 0;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_supported_caps(GENERIC_CHANNEL_CALLBACK* callback)
+{
+ RDPGFX_PLUGIN* gfx = NULL;
+ RdpgfxClientContext* context = NULL;
+ RDPGFX_CAPSET* capsSet = NULL;
+ RDPGFX_CAPSET capsSets[RDPGFX_NUMBER_CAPSETS] = { 0 };
+ RDPGFX_CAPS_ADVERTISE_PDU pdu = { 0 };
+
+ if (!callback)
+ return ERROR_BAD_ARGUMENTS;
+
+ gfx = (RDPGFX_PLUGIN*)callback->plugin;
+
+ if (!gfx)
+ return ERROR_BAD_CONFIGURATION;
+
+ context = gfx->context;
+
+ if (!context)
+ return ERROR_BAD_CONFIGURATION;
+
+ pdu.capsSetCount = 0;
+ pdu.capsSets = (RDPGFX_CAPSET*)capsSets;
+
+ if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_8))
+ {
+ capsSet = &capsSets[pdu.capsSetCount++];
+ capsSet->version = RDPGFX_CAPVERSION_8;
+ capsSet->length = 4;
+ capsSet->flags = 0;
+
+ if (freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxThinClient))
+ capsSet->flags |= RDPGFX_CAPS_FLAG_THINCLIENT;
+
+ /* in CAPVERSION_8 the spec says that we should not have both
+ * thinclient and smallcache (and thinclient implies a small cache)
+ */
+ if (freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxSmallCache) &&
+ !freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxThinClient))
+ capsSet->flags |= RDPGFX_CAPS_FLAG_SMALL_CACHE;
+ }
+
+ if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_81))
+ {
+ capsSet = &capsSets[pdu.capsSetCount++];
+ capsSet->version = RDPGFX_CAPVERSION_81;
+ capsSet->length = 4;
+ capsSet->flags = 0;
+
+ if (freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxThinClient))
+ capsSet->flags |= RDPGFX_CAPS_FLAG_THINCLIENT;
+
+ if (freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxSmallCache))
+ capsSet->flags |= RDPGFX_CAPS_FLAG_SMALL_CACHE;
+
+#ifdef WITH_GFX_H264
+
+ if (freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxH264))
+ capsSet->flags |= RDPGFX_CAPS_FLAG_AVC420_ENABLED;
+
+#endif
+ }
+
+ if (!freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxH264) ||
+ freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxAVC444))
+ {
+ UINT32 caps10Flags = 0;
+
+ if (freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxSmallCache))
+ caps10Flags |= RDPGFX_CAPS_FLAG_SMALL_CACHE;
+
+#ifdef WITH_GFX_H264
+
+ if (!freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxAVC444))
+ caps10Flags |= RDPGFX_CAPS_FLAG_AVC_DISABLED;
+
+#else
+ caps10Flags |= RDPGFX_CAPS_FLAG_AVC_DISABLED;
+#endif
+
+ if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_10))
+ {
+ capsSet = &capsSets[pdu.capsSetCount++];
+ capsSet->version = RDPGFX_CAPVERSION_10;
+ capsSet->length = 4;
+ capsSet->flags = caps10Flags;
+ }
+
+ if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_101))
+ {
+ capsSet = &capsSets[pdu.capsSetCount++];
+ capsSet->version = RDPGFX_CAPVERSION_101;
+ capsSet->length = 0x10;
+ capsSet->flags = 0;
+ }
+
+ if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_102))
+ {
+ capsSet = &capsSets[pdu.capsSetCount++];
+ capsSet->version = RDPGFX_CAPVERSION_102;
+ capsSet->length = 0x4;
+ capsSet->flags = caps10Flags;
+ }
+
+ if (freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxThinClient))
+ {
+ if ((caps10Flags & RDPGFX_CAPS_FLAG_AVC_DISABLED) == 0)
+ caps10Flags |= RDPGFX_CAPS_FLAG_AVC_THINCLIENT;
+ }
+
+ if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_103))
+ {
+ capsSet = &capsSets[pdu.capsSetCount++];
+ capsSet->version = RDPGFX_CAPVERSION_103;
+ capsSet->length = 0x4;
+ capsSet->flags = caps10Flags & ~RDPGFX_CAPS_FLAG_SMALL_CACHE;
+ }
+
+ if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_104))
+ {
+ capsSet = &capsSets[pdu.capsSetCount++];
+ capsSet->version = RDPGFX_CAPVERSION_104;
+ capsSet->length = 0x4;
+ capsSet->flags = caps10Flags;
+ }
+
+ /* The following capabilities expect support for image scaling.
+ * Disable these for builds that do not have support for that.
+ */
+#if defined(WITH_CAIRO) || defined(WITH_SWSCALE)
+ if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_105))
+ {
+ capsSet = &capsSets[pdu.capsSetCount++];
+ capsSet->version = RDPGFX_CAPVERSION_105;
+ capsSet->length = 0x4;
+ capsSet->flags = caps10Flags;
+ }
+
+ if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_106))
+ {
+ capsSet = &capsSets[pdu.capsSetCount++];
+ capsSet->version = RDPGFX_CAPVERSION_106;
+ capsSet->length = 0x4;
+ capsSet->flags = caps10Flags;
+ }
+
+ if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_106_ERR))
+ {
+ capsSet = &capsSets[pdu.capsSetCount++];
+ capsSet->version = RDPGFX_CAPVERSION_106_ERR;
+ capsSet->length = 0x4;
+ capsSet->flags = caps10Flags;
+ }
+#endif
+
+ if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_107))
+ {
+ capsSet = &capsSets[pdu.capsSetCount++];
+ capsSet->version = RDPGFX_CAPVERSION_107;
+ capsSet->length = 0x4;
+ capsSet->flags = caps10Flags;
+#if !defined(WITH_CAIRO) && !defined(WITH_SWSCALE)
+ capsSet->flags |= RDPGFX_CAPS_FLAG_SCALEDMAP_DISABLE;
+#endif
+ }
+ }
+
+ return IFCALLRESULT(ERROR_BAD_CONFIGURATION, context->CapsAdvertise, context, &pdu);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_caps_confirm_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ RDPGFX_CAPSET capsSet = { 0 };
+ RDPGFX_CAPS_CONFIRM_PDU pdu = { 0 };
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+
+ pdu.capsSet = &capsSet;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, capsSet.version); /* version (4 bytes) */
+ Stream_Read_UINT32(s, capsSet.length); /* capsDataLength (4 bytes) */
+ Stream_Read_UINT32(s, capsSet.flags); /* capsData (4 bytes) */
+ gfx->TotalDecodedFrames = 0;
+ gfx->ConnectionCaps = capsSet;
+ DEBUG_RDPGFX(gfx->log, "RecvCapsConfirmPdu: version: 0x%08" PRIX32 " flags: 0x%08" PRIX32 "",
+ capsSet.version, capsSet.flags);
+
+ if (!context)
+ return ERROR_BAD_CONFIGURATION;
+
+ return IFCALLRESULT(CHANNEL_RC_OK, context->CapsConfirm, context, &pdu);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_frame_acknowledge_pdu(RdpgfxClientContext* context,
+ const RDPGFX_FRAME_ACKNOWLEDGE_PDU* pdu)
+{
+ UINT error = 0;
+ wStream* s = NULL;
+ RDPGFX_HEADER header = { 0 };
+ RDPGFX_PLUGIN* gfx = NULL;
+ GENERIC_CHANNEL_CALLBACK* callback = NULL;
+
+ if (!context || !pdu)
+ return ERROR_BAD_ARGUMENTS;
+
+ gfx = (RDPGFX_PLUGIN*)context->handle;
+
+ if (!gfx || !gfx->base.listener_callback)
+ return ERROR_BAD_CONFIGURATION;
+
+ callback = gfx->base.listener_callback->channel_callback;
+
+ if (!callback)
+ return ERROR_BAD_CONFIGURATION;
+
+ header.flags = 0;
+ header.cmdId = RDPGFX_CMDID_FRAMEACKNOWLEDGE;
+ header.pduLength = RDPGFX_HEADER_SIZE + 12;
+ DEBUG_RDPGFX(gfx->log, "SendFrameAcknowledgePdu: %" PRIu32 "", pdu->frameId);
+ s = Stream_New(NULL, header.pduLength);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if ((error = rdpgfx_write_header(s, &header)))
+ goto fail;
+
+ /* RDPGFX_FRAME_ACKNOWLEDGE_PDU */
+ Stream_Write_UINT32(s, pdu->queueDepth); /* queueDepth (4 bytes) */
+ Stream_Write_UINT32(s, pdu->frameId); /* frameId (4 bytes) */
+ Stream_Write_UINT32(s, pdu->totalFramesDecoded); /* totalFramesDecoded (4 bytes) */
+ error = callback->channel->Write(callback->channel, (UINT32)Stream_Length(s), Stream_Buffer(s),
+ NULL);
+
+ if (error == CHANNEL_RC_OK) /* frame successfully acked */
+ gfx->UnacknowledgedFrames--;
+
+fail:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+static UINT rdpgfx_send_qoe_frame_acknowledge_pdu(RdpgfxClientContext* context,
+ const RDPGFX_QOE_FRAME_ACKNOWLEDGE_PDU* pdu)
+{
+ UINT error = 0;
+ wStream* s = NULL;
+ RDPGFX_HEADER header = { 0 };
+ GENERIC_CHANNEL_CALLBACK* callback = NULL;
+ RDPGFX_PLUGIN* gfx = NULL;
+
+ header.flags = 0;
+ header.cmdId = RDPGFX_CMDID_QOEFRAMEACKNOWLEDGE;
+ header.pduLength = RDPGFX_HEADER_SIZE + 12;
+
+ if (!context || !pdu)
+ return ERROR_BAD_ARGUMENTS;
+
+ gfx = (RDPGFX_PLUGIN*)context->handle;
+
+ if (!gfx || !gfx->base.listener_callback)
+ return ERROR_BAD_CONFIGURATION;
+
+ callback = gfx->base.listener_callback->channel_callback;
+
+ if (!callback)
+ return ERROR_BAD_CONFIGURATION;
+
+ DEBUG_RDPGFX(gfx->log, "SendQoeFrameAcknowledgePdu: %" PRIu32 "", pdu->frameId);
+ s = Stream_New(NULL, header.pduLength);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if ((error = rdpgfx_write_header(s, &header)))
+ goto fail;
+
+ /* RDPGFX_FRAME_ACKNOWLEDGE_PDU */
+ Stream_Write_UINT32(s, pdu->frameId);
+ Stream_Write_UINT32(s, pdu->timestamp);
+ Stream_Write_UINT16(s, pdu->timeDiffSE);
+ Stream_Write_UINT16(s, pdu->timeDiffEDR);
+ error = callback->channel->Write(callback->channel, (UINT32)Stream_Length(s), Stream_Buffer(s),
+ NULL);
+fail:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_reset_graphics_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ int pad = 0;
+ MONITOR_DEF* monitor = NULL;
+ RDPGFX_RESET_GRAPHICS_PDU pdu = { 0 };
+ WINPR_ASSERT(callback);
+
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+
+ WINPR_ASSERT(gfx);
+
+ RdpgfxClientContext* context = gfx->context;
+ UINT error = CHANNEL_RC_OK;
+ GraphicsResetEventArgs graphicsReset = { 0 };
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, pdu.width); /* width (4 bytes) */
+ Stream_Read_UINT32(s, pdu.height); /* height (4 bytes) */
+ Stream_Read_UINT32(s, pdu.monitorCount); /* monitorCount (4 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, pdu.monitorCount, 20ull))
+ return ERROR_INVALID_DATA;
+
+ pdu.monitorDefArray = (MONITOR_DEF*)calloc(pdu.monitorCount, sizeof(MONITOR_DEF));
+
+ if (!pdu.monitorDefArray)
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ for (UINT32 index = 0; index < pdu.monitorCount; index++)
+ {
+ monitor = &(pdu.monitorDefArray[index]);
+ Stream_Read_INT32(s, monitor->left); /* left (4 bytes) */
+ Stream_Read_INT32(s, monitor->top); /* top (4 bytes) */
+ Stream_Read_INT32(s, monitor->right); /* right (4 bytes) */
+ Stream_Read_INT32(s, monitor->bottom); /* bottom (4 bytes) */
+ Stream_Read_UINT32(s, monitor->flags); /* flags (4 bytes) */
+ }
+
+ pad = 340 - (RDPGFX_HEADER_SIZE + 12 + (pdu.monitorCount * 20));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)pad))
+ {
+ free(pdu.monitorDefArray);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Seek(s, pad); /* pad (total size is 340 bytes) */
+ DEBUG_RDPGFX(gfx->log,
+ "RecvResetGraphicsPdu: width: %" PRIu32 " height: %" PRIu32 " count: %" PRIu32 "",
+ pdu.width, pdu.height, pdu.monitorCount);
+
+#if defined(WITH_DEBUG_RDPGFX)
+ for (UINT32 index = 0; index < pdu.monitorCount; index++)
+ {
+ monitor = &(pdu.monitorDefArray[index]);
+ DEBUG_RDPGFX(gfx->log,
+ "RecvResetGraphicsPdu: monitor left:%" PRIi32 " top:%" PRIi32 " right:%" PRIi32
+ " bottom:%" PRIi32 " flags:0x%" PRIx32 "",
+ monitor->left, monitor->top, monitor->right, monitor->bottom, monitor->flags);
+ }
+#endif
+
+ if (context)
+ {
+ IFCALLRET(context->ResetGraphics, error, context, &pdu);
+
+ if (error)
+ WLog_Print(gfx->log, WLOG_ERROR, "context->ResetGraphics failed with error %" PRIu32 "",
+ error);
+ }
+
+ /* some listeners may be interested (namely the display channel) */
+ EventArgsInit(&graphicsReset, "libfreerdp");
+ graphicsReset.width = pdu.width;
+ graphicsReset.height = pdu.height;
+ PubSub_OnGraphicsReset(gfx->rdpcontext->pubSub, gfx->rdpcontext, &graphicsReset);
+ free(pdu.monitorDefArray);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_evict_cache_entry_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ RDPGFX_EVICT_CACHE_ENTRY_PDU pdu = { 0 };
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.cacheSlot); /* cacheSlot (2 bytes) */
+ WLog_Print(gfx->log, WLOG_DEBUG, "RecvEvictCacheEntryPdu: cacheSlot: %" PRIu16 "",
+ pdu.cacheSlot);
+
+ if (context)
+ {
+ IFCALLRET(context->EvictCacheEntry, error, context, &pdu);
+
+ if (error)
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "context->EvictCacheEntry failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+/**
+ * Load cache import offer from file (offline replay)
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_load_cache_import_offer(RDPGFX_PLUGIN* gfx, RDPGFX_CACHE_IMPORT_OFFER_PDU* offer)
+{
+ int count = 0;
+ UINT error = CHANNEL_RC_OK;
+ PERSISTENT_CACHE_ENTRY entry;
+ rdpPersistentCache* persistent = NULL;
+ WINPR_ASSERT(gfx);
+ WINPR_ASSERT(gfx->rdpcontext);
+ rdpSettings* settings = gfx->rdpcontext->settings;
+
+ WINPR_ASSERT(offer);
+ WINPR_ASSERT(settings);
+
+ offer->cacheEntriesCount = 0;
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_BitmapCachePersistEnabled))
+ return CHANNEL_RC_OK;
+
+ const char* BitmapCachePersistFile =
+ freerdp_settings_get_string(settings, FreeRDP_BitmapCachePersistFile);
+ if (!BitmapCachePersistFile)
+ return CHANNEL_RC_OK;
+
+ persistent = persistent_cache_new();
+
+ if (!persistent)
+ return CHANNEL_RC_NO_MEMORY;
+
+ if (persistent_cache_open(persistent, BitmapCachePersistFile, FALSE, 3) < 1)
+ {
+ error = CHANNEL_RC_INITIALIZATION_ERROR;
+ goto fail;
+ }
+
+ if (persistent_cache_get_version(persistent) != 3)
+ {
+ error = ERROR_INVALID_DATA;
+ goto fail;
+ }
+
+ count = persistent_cache_get_count(persistent);
+
+ if (count < 1)
+ {
+ error = ERROR_INVALID_DATA;
+ goto fail;
+ }
+
+ if (count >= RDPGFX_CACHE_ENTRY_MAX_COUNT)
+ count = RDPGFX_CACHE_ENTRY_MAX_COUNT - 1;
+
+ if (count > gfx->MaxCacheSlots)
+ count = gfx->MaxCacheSlots;
+
+ offer->cacheEntriesCount = (UINT16)count;
+
+ for (int idx = 0; idx < count; idx++)
+ {
+ if (persistent_cache_read_entry(persistent, &entry) < 1)
+ {
+ error = ERROR_INVALID_DATA;
+ goto fail;
+ }
+
+ offer->cacheEntries[idx].cacheKey = entry.key64;
+ offer->cacheEntries[idx].bitmapLength = entry.size;
+ }
+
+ persistent_cache_free(persistent);
+
+ return error;
+fail:
+ persistent_cache_free(persistent);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_save_persistent_cache(RDPGFX_PLUGIN* gfx)
+{
+ UINT error = CHANNEL_RC_OK;
+ PERSISTENT_CACHE_ENTRY cacheEntry;
+ rdpPersistentCache* persistent = NULL;
+ WINPR_ASSERT(gfx);
+ WINPR_ASSERT(gfx->rdpcontext);
+ rdpSettings* settings = gfx->rdpcontext->settings;
+ RdpgfxClientContext* context = gfx->context;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(settings);
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_BitmapCachePersistEnabled))
+ return CHANNEL_RC_OK;
+
+ const char* BitmapCachePersistFile =
+ freerdp_settings_get_string(settings, FreeRDP_BitmapCachePersistFile);
+ if (!BitmapCachePersistFile)
+ return CHANNEL_RC_OK;
+
+ if (!context->ExportCacheEntry)
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+
+ persistent = persistent_cache_new();
+
+ if (!persistent)
+ return CHANNEL_RC_NO_MEMORY;
+
+ if (persistent_cache_open(persistent, BitmapCachePersistFile, TRUE, 3) < 1)
+ {
+ error = CHANNEL_RC_INITIALIZATION_ERROR;
+ goto fail;
+ }
+
+ for (UINT16 idx = 0; idx < gfx->MaxCacheSlots; idx++)
+ {
+ if (gfx->CacheSlots[idx])
+ {
+ UINT16 cacheSlot = (UINT16)idx;
+
+ if (context->ExportCacheEntry(context, cacheSlot, &cacheEntry) != CHANNEL_RC_OK)
+ continue;
+
+ persistent_cache_write_entry(persistent, &cacheEntry);
+ }
+ }
+
+ persistent_cache_free(persistent);
+
+ return error;
+fail:
+ persistent_cache_free(persistent);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_cache_import_offer_pdu(RdpgfxClientContext* context,
+ const RDPGFX_CACHE_IMPORT_OFFER_PDU* pdu)
+{
+ UINT error = CHANNEL_RC_OK;
+ wStream* s = NULL;
+ RDPGFX_HEADER header;
+ GENERIC_CHANNEL_CALLBACK* callback = NULL;
+ WINPR_ASSERT(context);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)context->handle;
+
+ if (!context || !pdu)
+ return ERROR_BAD_ARGUMENTS;
+
+ gfx = (RDPGFX_PLUGIN*)context->handle;
+
+ if (!gfx || !gfx->base.listener_callback)
+ return ERROR_BAD_CONFIGURATION;
+
+ callback = gfx->base.listener_callback->channel_callback;
+
+ if (!callback)
+ return ERROR_BAD_CONFIGURATION;
+
+ header.flags = 0;
+ header.cmdId = RDPGFX_CMDID_CACHEIMPORTOFFER;
+ header.pduLength = RDPGFX_HEADER_SIZE + 2 + pdu->cacheEntriesCount * 12;
+ DEBUG_RDPGFX(gfx->log, "SendCacheImportOfferPdu: cacheEntriesCount: %" PRIu16 "",
+ pdu->cacheEntriesCount);
+ s = Stream_New(NULL, header.pduLength);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if ((error = rdpgfx_write_header(s, &header)))
+ goto fail;
+
+ if (pdu->cacheEntriesCount <= 0)
+ {
+ WLog_ERR(TAG, "Invalid cacheEntriesCount: %" PRIu16 "", pdu->cacheEntriesCount);
+ error = ERROR_INVALID_DATA;
+ goto fail;
+ }
+
+ /* cacheEntriesCount (2 bytes) */
+ Stream_Write_UINT16(s, pdu->cacheEntriesCount);
+
+ for (UINT16 index = 0; index < pdu->cacheEntriesCount; index++)
+ {
+ const RDPGFX_CACHE_ENTRY_METADATA* cacheEntry = &(pdu->cacheEntries[index]);
+ Stream_Write_UINT64(s, cacheEntry->cacheKey); /* cacheKey (8 bytes) */
+ Stream_Write_UINT32(s, cacheEntry->bitmapLength); /* bitmapLength (4 bytes) */
+ }
+
+ error = callback->channel->Write(callback->channel, (UINT32)Stream_Length(s), Stream_Buffer(s),
+ NULL);
+
+fail:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_cache_offer(RDPGFX_PLUGIN* gfx)
+{
+ int count = 0;
+ UINT error = CHANNEL_RC_OK;
+ PERSISTENT_CACHE_ENTRY entry;
+ RDPGFX_CACHE_IMPORT_OFFER_PDU* offer = NULL;
+ rdpPersistentCache* persistent = NULL;
+
+ WINPR_ASSERT(gfx);
+ WINPR_ASSERT(gfx->rdpcontext);
+
+ RdpgfxClientContext* context = gfx->context;
+ rdpSettings* settings = gfx->rdpcontext->settings;
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_BitmapCachePersistEnabled))
+ return CHANNEL_RC_OK;
+
+ const char* BitmapCachePersistFile =
+ freerdp_settings_get_string(settings, FreeRDP_BitmapCachePersistFile);
+ if (!BitmapCachePersistFile)
+ return CHANNEL_RC_OK;
+
+ persistent = persistent_cache_new();
+
+ if (!persistent)
+ return CHANNEL_RC_NO_MEMORY;
+
+ if (persistent_cache_open(persistent, BitmapCachePersistFile, FALSE, 3) < 1)
+ {
+ error = CHANNEL_RC_INITIALIZATION_ERROR;
+ goto fail;
+ }
+
+ if (persistent_cache_get_version(persistent) != 3)
+ {
+ error = ERROR_INVALID_DATA;
+ goto fail;
+ }
+
+ count = persistent_cache_get_count(persistent);
+
+ if (count >= RDPGFX_CACHE_ENTRY_MAX_COUNT)
+ count = RDPGFX_CACHE_ENTRY_MAX_COUNT - 1;
+
+ if (count > gfx->MaxCacheSlots)
+ count = gfx->MaxCacheSlots;
+
+ offer = (RDPGFX_CACHE_IMPORT_OFFER_PDU*)calloc(1, sizeof(RDPGFX_CACHE_IMPORT_OFFER_PDU));
+ if (!offer)
+ {
+ error = CHANNEL_RC_NO_MEMORY;
+ goto fail;
+ }
+
+ offer->cacheEntriesCount = (UINT16)count;
+
+ WLog_DBG(TAG, "Sending Cache Import Offer: %d", count);
+
+ for (int idx = 0; idx < count; idx++)
+ {
+ if (persistent_cache_read_entry(persistent, &entry) < 1)
+ {
+ error = ERROR_INVALID_DATA;
+ goto fail;
+ }
+
+ offer->cacheEntries[idx].cacheKey = entry.key64;
+ offer->cacheEntries[idx].bitmapLength = entry.size;
+ }
+
+ if (offer->cacheEntriesCount > 0)
+ {
+ error = rdpgfx_send_cache_import_offer_pdu(context, offer);
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "Failed to send cache import offer PDU");
+ goto fail;
+ }
+ }
+
+fail:
+ persistent_cache_free(persistent);
+ free(offer);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_load_cache_import_reply(RDPGFX_PLUGIN* gfx,
+ const RDPGFX_CACHE_IMPORT_REPLY_PDU* reply)
+{
+ int count = 0;
+ UINT error = CHANNEL_RC_OK;
+ rdpPersistentCache* persistent = NULL;
+ WINPR_ASSERT(gfx);
+ WINPR_ASSERT(gfx->rdpcontext);
+ rdpSettings* settings = gfx->rdpcontext->settings;
+ RdpgfxClientContext* context = gfx->context;
+
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(reply);
+ if (!freerdp_settings_get_bool(settings, FreeRDP_BitmapCachePersistEnabled))
+ return CHANNEL_RC_OK;
+
+ const char* BitmapCachePersistFile =
+ freerdp_settings_get_string(settings, FreeRDP_BitmapCachePersistFile);
+ if (!BitmapCachePersistFile)
+ return CHANNEL_RC_OK;
+
+ persistent = persistent_cache_new();
+
+ if (!persistent)
+ return CHANNEL_RC_NO_MEMORY;
+
+ if (persistent_cache_open(persistent, BitmapCachePersistFile, FALSE, 3) < 1)
+ {
+ error = CHANNEL_RC_INITIALIZATION_ERROR;
+ goto fail;
+ }
+
+ if (persistent_cache_get_version(persistent) != 3)
+ {
+ error = ERROR_INVALID_DATA;
+ goto fail;
+ }
+
+ count = persistent_cache_get_count(persistent);
+
+ count = (count < reply->importedEntriesCount) ? count : reply->importedEntriesCount;
+
+ WLog_DBG(TAG, "Receiving Cache Import Reply: %d", count);
+
+ for (int idx = 0; idx < count; idx++)
+ {
+ PERSISTENT_CACHE_ENTRY entry = { 0 };
+ if (persistent_cache_read_entry(persistent, &entry) < 1)
+ {
+ error = ERROR_INVALID_DATA;
+ goto fail;
+ }
+
+ const UINT16 cacheSlot = reply->cacheSlots[idx];
+ if (context && context->ImportCacheEntry)
+ context->ImportCacheEntry(context, cacheSlot, &entry);
+ }
+
+ persistent_cache_free(persistent);
+
+ return error;
+fail:
+ persistent_cache_free(persistent);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_cache_import_reply_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ RDPGFX_CACHE_IMPORT_REPLY_PDU pdu = { 0 };
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.importedEntriesCount); /* cacheSlot (2 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, pdu.importedEntriesCount, 2ull))
+ return ERROR_INVALID_DATA;
+
+ if (pdu.importedEntriesCount > RDPGFX_CACHE_ENTRY_MAX_COUNT)
+ return ERROR_INVALID_DATA;
+
+ for (UINT16 idx = 0; idx < pdu.importedEntriesCount; idx++)
+ {
+ Stream_Read_UINT16(s, pdu.cacheSlots[idx]); /* cacheSlot (2 bytes) */
+ }
+
+ DEBUG_RDPGFX(gfx->log, "RecvCacheImportReplyPdu: importedEntriesCount: %" PRIu16 "",
+ pdu.importedEntriesCount);
+
+ error = rdpgfx_load_cache_import_reply(gfx, &pdu);
+
+ if (error)
+ {
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_load_cache_import_reply failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ if (context)
+ {
+ IFCALLRET(context->CacheImportReply, error, context, &pdu);
+
+ if (error)
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "context->CacheImportReply failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_create_surface_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ RDPGFX_CREATE_SURFACE_PDU pdu = { 0 };
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 7))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */
+ Stream_Read_UINT16(s, pdu.width); /* width (2 bytes) */
+ Stream_Read_UINT16(s, pdu.height); /* height (2 bytes) */
+ Stream_Read_UINT8(s, pdu.pixelFormat); /* RDPGFX_PIXELFORMAT (1 byte) */
+ DEBUG_RDPGFX(gfx->log,
+ "RecvCreateSurfacePdu: surfaceId: %" PRIu16 " width: %" PRIu16 " height: %" PRIu16
+ " pixelFormat: 0x%02" PRIX8 "",
+ pdu.surfaceId, pdu.width, pdu.height, pdu.pixelFormat);
+
+ if (context)
+ {
+ /* create surface PDU sometimes happens for surface ID that are already in use and have not
+ * been removed yet. Ensure that there is no surface with the new ID by trying to remove it
+ * manually.
+ */
+ RDPGFX_DELETE_SURFACE_PDU deletePdu = { pdu.surfaceId };
+ IFCALL(context->DeleteSurface, context, &deletePdu);
+
+ IFCALLRET(context->CreateSurface, error, context, &pdu);
+
+ if (error)
+ WLog_Print(gfx->log, WLOG_ERROR, "context->CreateSurface failed with error %" PRIu32 "",
+ error);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_delete_surface_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ RDPGFX_DELETE_SURFACE_PDU pdu = { 0 };
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */
+ DEBUG_RDPGFX(gfx->log, "RecvDeleteSurfacePdu: surfaceId: %" PRIu16 "", pdu.surfaceId);
+
+ if (context)
+ {
+ IFCALLRET(context->DeleteSurface, error, context, &pdu);
+
+ if (error)
+ WLog_Print(gfx->log, WLOG_ERROR, "context->DeleteSurface failed with error %" PRIu32 "",
+ error);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_start_frame_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ RDPGFX_START_FRAME_PDU pdu = { 0 };
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RDPGFX_START_FRAME_PDU_SIZE))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, pdu.timestamp); /* timestamp (4 bytes) */
+ Stream_Read_UINT32(s, pdu.frameId); /* frameId (4 bytes) */
+ DEBUG_RDPGFX(gfx->log, "RecvStartFramePdu: frameId: %" PRIu32 " timestamp: 0x%08" PRIX32 "",
+ pdu.frameId, pdu.timestamp);
+ gfx->StartDecodingTime = GetTickCount64();
+
+ if (context)
+ {
+ IFCALLRET(context->StartFrame, error, context, &pdu);
+
+ if (error)
+ WLog_Print(gfx->log, WLOG_ERROR, "context->StartFrame failed with error %" PRIu32 "",
+ error);
+ }
+
+ gfx->UnacknowledgedFrames++;
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_end_frame_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ RDPGFX_END_FRAME_PDU pdu = { 0 };
+ RDPGFX_FRAME_ACKNOWLEDGE_PDU ack = { 0 };
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RDPGFX_END_FRAME_PDU_SIZE))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, pdu.frameId); /* frameId (4 bytes) */
+ DEBUG_RDPGFX(gfx->log, "RecvEndFramePdu: frameId: %" PRIu32 "", pdu.frameId);
+
+ if (context)
+ {
+ IFCALLRET(context->EndFrame, error, context, &pdu);
+
+ if (error)
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "context->EndFrame failed with error %" PRIu32 "",
+ error);
+ return error;
+ }
+ }
+
+ gfx->TotalDecodedFrames++;
+
+ if (!gfx->sendFrameAcks)
+ return error;
+
+ ack.frameId = pdu.frameId;
+ ack.totalFramesDecoded = gfx->TotalDecodedFrames;
+
+ if (gfx->suspendFrameAcks)
+ {
+ ack.queueDepth = SUSPEND_FRAME_ACKNOWLEDGEMENT;
+
+ if (gfx->TotalDecodedFrames == 1)
+ if ((error = rdpgfx_send_frame_acknowledge_pdu(context, &ack)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_send_frame_acknowledge_pdu failed with error %" PRIu32 "",
+ error);
+ }
+ else
+ {
+ ack.queueDepth = QUEUE_DEPTH_UNAVAILABLE;
+
+ if ((error = rdpgfx_send_frame_acknowledge_pdu(context, &ack)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_send_frame_acknowledge_pdu failed with error %" PRIu32 "", error);
+ }
+
+ switch (gfx->ConnectionCaps.version)
+ {
+ case RDPGFX_CAPVERSION_10:
+ case RDPGFX_CAPVERSION_102:
+ case RDPGFX_CAPVERSION_103:
+ case RDPGFX_CAPVERSION_104:
+ case RDPGFX_CAPVERSION_105:
+ case RDPGFX_CAPVERSION_106:
+ case RDPGFX_CAPVERSION_106_ERR:
+ case RDPGFX_CAPVERSION_107:
+ if (freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxSendQoeAck))
+ {
+ RDPGFX_QOE_FRAME_ACKNOWLEDGE_PDU qoe;
+ UINT64 diff = (GetTickCount64() - gfx->StartDecodingTime);
+
+ if (diff > 65000)
+ diff = 0;
+
+ qoe.frameId = pdu.frameId;
+ qoe.timestamp = gfx->StartDecodingTime;
+ qoe.timeDiffSE = diff;
+ qoe.timeDiffEDR = 1;
+
+ if ((error = rdpgfx_send_qoe_frame_acknowledge_pdu(context, &qoe)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_send_qoe_frame_acknowledge_pdu failed with error %" PRIu32
+ "",
+ error);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_wire_to_surface_1_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ RDPGFX_SURFACE_COMMAND cmd = { 0 };
+ RDPGFX_WIRE_TO_SURFACE_PDU_1 pdu = { 0 };
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+ UINT error = 0;
+
+ WINPR_ASSERT(gfx);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RDPGFX_WIRE_TO_SURFACE_PDU_1_SIZE))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */
+ Stream_Read_UINT16(s, pdu.codecId); /* codecId (2 bytes) */
+ Stream_Read_UINT8(s, pdu.pixelFormat); /* pixelFormat (1 byte) */
+
+ if ((error = rdpgfx_read_rect16(s, &(pdu.destRect)))) /* destRect (8 bytes) */
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_rect16 failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ Stream_Read_UINT32(s, pdu.bitmapDataLength); /* bitmapDataLength (4 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, pdu.bitmapDataLength))
+ return ERROR_INVALID_DATA;
+
+ pdu.bitmapData = Stream_Pointer(s);
+ Stream_Seek(s, pdu.bitmapDataLength);
+
+ DEBUG_RDPGFX(gfx->log,
+ "RecvWireToSurface1Pdu: surfaceId: %" PRIu16 " codecId: %s (0x%04" PRIX16
+ ") pixelFormat: 0x%02" PRIX8 " "
+ "destRect: left: %" PRIu16 " top: %" PRIu16 " right: %" PRIu16 " bottom: %" PRIu16
+ " bitmapDataLength: %" PRIu32 "",
+ pdu.surfaceId, rdpgfx_get_codec_id_string(pdu.codecId), pdu.codecId,
+ pdu.pixelFormat, pdu.destRect.left, pdu.destRect.top, pdu.destRect.right,
+ pdu.destRect.bottom, pdu.bitmapDataLength);
+ cmd.surfaceId = pdu.surfaceId;
+ cmd.codecId = pdu.codecId;
+ cmd.contextId = 0;
+
+ switch (pdu.pixelFormat)
+ {
+ case GFX_PIXEL_FORMAT_XRGB_8888:
+ cmd.format = PIXEL_FORMAT_BGRX32;
+ break;
+
+ case GFX_PIXEL_FORMAT_ARGB_8888:
+ cmd.format = PIXEL_FORMAT_BGRA32;
+ break;
+
+ default:
+ return ERROR_INVALID_DATA;
+ }
+
+ cmd.left = pdu.destRect.left;
+ cmd.top = pdu.destRect.top;
+ cmd.right = pdu.destRect.right;
+ cmd.bottom = pdu.destRect.bottom;
+ cmd.width = cmd.right - cmd.left;
+ cmd.height = cmd.bottom - cmd.top;
+ cmd.length = pdu.bitmapDataLength;
+ cmd.data = pdu.bitmapData;
+ cmd.extra = NULL;
+
+ if (cmd.right < cmd.left)
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "RecvWireToSurface1Pdu right=%" PRIu32 " < left=%" PRIu32,
+ cmd.right, cmd.left);
+ return ERROR_INVALID_DATA;
+ }
+ if (cmd.bottom < cmd.top)
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "RecvWireToSurface1Pdu bottom=%" PRIu32 " < top=%" PRIu32,
+ cmd.bottom, cmd.top);
+ return ERROR_INVALID_DATA;
+ }
+
+ if ((error = rdpgfx_decode(gfx, &cmd)))
+ WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_decode failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_wire_to_surface_2_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ RDPGFX_SURFACE_COMMAND cmd = { 0 };
+ RDPGFX_WIRE_TO_SURFACE_PDU_2 pdu = { 0 };
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, RDPGFX_WIRE_TO_SURFACE_PDU_2_SIZE))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */
+ Stream_Read_UINT16(s, pdu.codecId); /* codecId (2 bytes) */
+ Stream_Read_UINT32(s, pdu.codecContextId); /* codecContextId (4 bytes) */
+ Stream_Read_UINT8(s, pdu.pixelFormat); /* pixelFormat (1 byte) */
+ Stream_Read_UINT32(s, pdu.bitmapDataLength); /* bitmapDataLength (4 bytes) */
+ pdu.bitmapData = Stream_Pointer(s);
+ Stream_Seek(s, pdu.bitmapDataLength);
+
+ DEBUG_RDPGFX(gfx->log,
+ "RecvWireToSurface2Pdu: surfaceId: %" PRIu16 " codecId: %s (0x%04" PRIX16 ") "
+ "codecContextId: %" PRIu32 " pixelFormat: 0x%02" PRIX8
+ " bitmapDataLength: %" PRIu32 "",
+ pdu.surfaceId, rdpgfx_get_codec_id_string(pdu.codecId), pdu.codecId,
+ pdu.codecContextId, pdu.pixelFormat, pdu.bitmapDataLength);
+
+ cmd.surfaceId = pdu.surfaceId;
+ cmd.codecId = pdu.codecId;
+ cmd.contextId = pdu.codecContextId;
+
+ switch (pdu.pixelFormat)
+ {
+ case GFX_PIXEL_FORMAT_XRGB_8888:
+ cmd.format = PIXEL_FORMAT_BGRX32;
+ break;
+
+ case GFX_PIXEL_FORMAT_ARGB_8888:
+ cmd.format = PIXEL_FORMAT_BGRA32;
+ break;
+
+ default:
+ return ERROR_INVALID_DATA;
+ }
+
+ cmd.left = 0;
+ cmd.top = 0;
+ cmd.right = 0;
+ cmd.bottom = 0;
+ cmd.width = 0;
+ cmd.height = 0;
+ cmd.length = pdu.bitmapDataLength;
+ cmd.data = pdu.bitmapData;
+ cmd.extra = NULL;
+
+ if (context)
+ {
+ IFCALLRET(context->SurfaceCommand, error, context, &cmd);
+
+ if (error)
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "context->SurfaceCommand failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_delete_encoding_context_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ RDPGFX_DELETE_ENCODING_CONTEXT_PDU pdu = { 0 };
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */
+ Stream_Read_UINT32(s, pdu.codecContextId); /* codecContextId (4 bytes) */
+
+ DEBUG_RDPGFX(gfx->log,
+ "RecvDeleteEncodingContextPdu: surfaceId: %" PRIu16 " codecContextId: %" PRIu32 "",
+ pdu.surfaceId, pdu.codecContextId);
+
+ if (context)
+ {
+ IFCALLRET(context->DeleteEncodingContext, error, context, &pdu);
+
+ if (error)
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "context->DeleteEncodingContext failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_solid_fill_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ RECTANGLE_16* fillRect = NULL;
+ RDPGFX_SOLID_FILL_PDU pdu = { 0 };
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+ UINT error = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */
+
+ if ((error = rdpgfx_read_color32(s, &(pdu.fillPixel)))) /* fillPixel (4 bytes) */
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_color32 failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ Stream_Read_UINT16(s, pdu.fillRectCount); /* fillRectCount (2 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, pdu.fillRectCount, 8ull))
+ return ERROR_INVALID_DATA;
+
+ pdu.fillRects = (RECTANGLE_16*)calloc(pdu.fillRectCount, sizeof(RECTANGLE_16));
+
+ if (!pdu.fillRects)
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ for (UINT16 index = 0; index < pdu.fillRectCount; index++)
+ {
+ fillRect = &(pdu.fillRects[index]);
+
+ if ((error = rdpgfx_read_rect16(s, fillRect)))
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_rect16 failed with error %" PRIu32 "!",
+ error);
+ free(pdu.fillRects);
+ return error;
+ }
+ }
+ DEBUG_RDPGFX(gfx->log, "RecvSolidFillPdu: surfaceId: %" PRIu16 " fillRectCount: %" PRIu16 "",
+ pdu.surfaceId, pdu.fillRectCount);
+
+ if (context)
+ {
+ IFCALLRET(context->SolidFill, error, context, &pdu);
+
+ if (error)
+ WLog_Print(gfx->log, WLOG_ERROR, "context->SolidFill failed with error %" PRIu32 "",
+ error);
+ }
+
+ free(pdu.fillRects);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_surface_to_surface_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ RDPGFX_POINT16* destPt = NULL;
+ RDPGFX_SURFACE_TO_SURFACE_PDU pdu = { 0 };
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+ UINT error = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 14))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.surfaceIdSrc); /* surfaceIdSrc (2 bytes) */
+ Stream_Read_UINT16(s, pdu.surfaceIdDest); /* surfaceIdDest (2 bytes) */
+
+ if ((error = rdpgfx_read_rect16(s, &(pdu.rectSrc)))) /* rectSrc (8 bytes ) */
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_rect16 failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ Stream_Read_UINT16(s, pdu.destPtsCount); /* destPtsCount (2 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, pdu.destPtsCount, 4ull))
+ return ERROR_INVALID_DATA;
+
+ pdu.destPts = (RDPGFX_POINT16*)calloc(pdu.destPtsCount, sizeof(RDPGFX_POINT16));
+
+ if (!pdu.destPts)
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ for (UINT16 index = 0; index < pdu.destPtsCount; index++)
+ {
+ destPt = &(pdu.destPts[index]);
+
+ if ((error = rdpgfx_read_point16(s, destPt)))
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_point16 failed with error %" PRIu32 "!",
+ error);
+ free(pdu.destPts);
+ return error;
+ }
+ }
+
+ DEBUG_RDPGFX(gfx->log,
+ "RecvSurfaceToSurfacePdu: surfaceIdSrc: %" PRIu16 " surfaceIdDest: %" PRIu16 " "
+ "left: %" PRIu16 " top: %" PRIu16 " right: %" PRIu16 " bottom: %" PRIu16
+ " destPtsCount: %" PRIu16 "",
+ pdu.surfaceIdSrc, pdu.surfaceIdDest, pdu.rectSrc.left, pdu.rectSrc.top,
+ pdu.rectSrc.right, pdu.rectSrc.bottom, pdu.destPtsCount);
+
+ if (context)
+ {
+ IFCALLRET(context->SurfaceToSurface, error, context, &pdu);
+
+ if (error)
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "context->SurfaceToSurface failed with error %" PRIu32 "", error);
+ }
+
+ free(pdu.destPts);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_surface_to_cache_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ RDPGFX_SURFACE_TO_CACHE_PDU pdu = { 0 };
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+ UINT error = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 20))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */
+ Stream_Read_UINT64(s, pdu.cacheKey); /* cacheKey (8 bytes) */
+ Stream_Read_UINT16(s, pdu.cacheSlot); /* cacheSlot (2 bytes) */
+
+ if ((error = rdpgfx_read_rect16(s, &(pdu.rectSrc)))) /* rectSrc (8 bytes ) */
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_rect16 failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ DEBUG_RDPGFX(gfx->log,
+ "RecvSurfaceToCachePdu: surfaceId: %" PRIu16 " cacheKey: 0x%016" PRIX64
+ " cacheSlot: %" PRIu16 " "
+ "left: %" PRIu16 " top: %" PRIu16 " right: %" PRIu16 " bottom: %" PRIu16 "",
+ pdu.surfaceId, pdu.cacheKey, pdu.cacheSlot, pdu.rectSrc.left, pdu.rectSrc.top,
+ pdu.rectSrc.right, pdu.rectSrc.bottom);
+
+ if (context)
+ {
+ IFCALLRET(context->SurfaceToCache, error, context, &pdu);
+
+ if (error)
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "context->SurfaceToCache failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_cache_to_surface_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ RDPGFX_POINT16* destPt = NULL;
+ RDPGFX_CACHE_TO_SURFACE_PDU pdu = { 0 };
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.cacheSlot); /* cacheSlot (2 bytes) */
+ Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */
+ Stream_Read_UINT16(s, pdu.destPtsCount); /* destPtsCount (2 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, pdu.destPtsCount, 4ull))
+ return ERROR_INVALID_DATA;
+
+ pdu.destPts = (RDPGFX_POINT16*)calloc(pdu.destPtsCount, sizeof(RDPGFX_POINT16));
+
+ if (!pdu.destPts)
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ for (UINT16 index = 0; index < pdu.destPtsCount; index++)
+ {
+ destPt = &(pdu.destPts[index]);
+
+ if ((error = rdpgfx_read_point16(s, destPt)))
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_point16 failed with error %" PRIu32 "",
+ error);
+ free(pdu.destPts);
+ return error;
+ }
+ }
+
+ DEBUG_RDPGFX(gfx->log,
+ "RdpGfxRecvCacheToSurfacePdu: cacheSlot: %" PRIu16 " surfaceId: %" PRIu16
+ " destPtsCount: %" PRIu16 "",
+ pdu.cacheSlot, pdu.surfaceId, pdu.destPtsCount);
+
+ if (context)
+ {
+ IFCALLRET(context->CacheToSurface, error, context, &pdu);
+
+ if (error)
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "context->CacheToSurface failed with error %" PRIu32 "", error);
+ }
+
+ free(pdu.destPts);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_map_surface_to_output_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ RDPGFX_MAP_SURFACE_TO_OUTPUT_PDU pdu = { 0 };
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */
+ Stream_Read_UINT16(s, pdu.reserved); /* reserved (2 bytes) */
+ Stream_Read_UINT32(s, pdu.outputOriginX); /* outputOriginX (4 bytes) */
+ Stream_Read_UINT32(s, pdu.outputOriginY); /* outputOriginY (4 bytes) */
+ DEBUG_RDPGFX(gfx->log,
+ "RecvMapSurfaceToOutputPdu: surfaceId: %" PRIu16 " outputOriginX: %" PRIu32
+ " outputOriginY: %" PRIu32 "",
+ pdu.surfaceId, pdu.outputOriginX, pdu.outputOriginY);
+
+ if (context)
+ {
+ IFCALLRET(context->MapSurfaceToOutput, error, context, &pdu);
+
+ if (error)
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "context->MapSurfaceToOutput failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+static UINT rdpgfx_recv_map_surface_to_scaled_output_pdu(GENERIC_CHANNEL_CALLBACK* callback,
+ wStream* s)
+{
+ RDPGFX_MAP_SURFACE_TO_SCALED_OUTPUT_PDU pdu = { 0 };
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 20))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */
+ Stream_Read_UINT16(s, pdu.reserved); /* reserved (2 bytes) */
+ Stream_Read_UINT32(s, pdu.outputOriginX); /* outputOriginX (4 bytes) */
+ Stream_Read_UINT32(s, pdu.outputOriginY); /* outputOriginY (4 bytes) */
+ Stream_Read_UINT32(s, pdu.targetWidth); /* targetWidth (4 bytes) */
+ Stream_Read_UINT32(s, pdu.targetHeight); /* targetHeight (4 bytes) */
+ DEBUG_RDPGFX(gfx->log,
+ "RecvMapSurfaceToScaledOutputPdu: surfaceId: %" PRIu16 " outputOriginX: %" PRIu32
+ " outputOriginY: %" PRIu32 " targetWidth: %" PRIu32 " targetHeight: %" PRIu32,
+ pdu.surfaceId, pdu.outputOriginX, pdu.outputOriginY, pdu.targetWidth,
+ pdu.targetHeight);
+
+ if (context)
+ {
+ IFCALLRET(context->MapSurfaceToScaledOutput, error, context, &pdu);
+
+ if (error)
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "context->MapSurfaceToScaledOutput failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_map_surface_to_window_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ RDPGFX_MAP_SURFACE_TO_WINDOW_PDU pdu = { 0 };
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 18))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */
+ Stream_Read_UINT64(s, pdu.windowId); /* windowId (8 bytes) */
+ Stream_Read_UINT32(s, pdu.mappedWidth); /* mappedWidth (4 bytes) */
+ Stream_Read_UINT32(s, pdu.mappedHeight); /* mappedHeight (4 bytes) */
+ DEBUG_RDPGFX(gfx->log,
+ "RecvMapSurfaceToWindowPdu: surfaceId: %" PRIu16 " windowId: 0x%016" PRIX64
+ " mappedWidth: %" PRIu32 " mappedHeight: %" PRIu32 "",
+ pdu.surfaceId, pdu.windowId, pdu.mappedWidth, pdu.mappedHeight);
+
+ if (context && context->MapSurfaceToWindow)
+ {
+ IFCALLRET(context->MapSurfaceToWindow, error, context, &pdu);
+
+ if (error)
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "context->MapSurfaceToWindow failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+static UINT rdpgfx_recv_map_surface_to_scaled_window_pdu(GENERIC_CHANNEL_CALLBACK* callback,
+ wStream* s)
+{
+ RDPGFX_MAP_SURFACE_TO_SCALED_WINDOW_PDU pdu = { 0 };
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 26))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */
+ Stream_Read_UINT64(s, pdu.windowId); /* windowId (8 bytes) */
+ Stream_Read_UINT32(s, pdu.mappedWidth); /* mappedWidth (4 bytes) */
+ Stream_Read_UINT32(s, pdu.mappedHeight); /* mappedHeight (4 bytes) */
+ Stream_Read_UINT32(s, pdu.targetWidth); /* targetWidth (4 bytes) */
+ Stream_Read_UINT32(s, pdu.targetHeight); /* targetHeight (4 bytes) */
+ DEBUG_RDPGFX(gfx->log,
+ "RecvMapSurfaceToScaledWindowPdu: surfaceId: %" PRIu16 " windowId: 0x%016" PRIX64
+ " mappedWidth: %" PRIu32 " mappedHeight: %" PRIu32 " targetWidth: %" PRIu32
+ " targetHeight: %" PRIu32 "",
+ pdu.surfaceId, pdu.windowId, pdu.mappedWidth, pdu.mappedHeight, pdu.targetWidth,
+ pdu.targetHeight);
+
+ if (context && context->MapSurfaceToScaledWindow)
+ {
+ IFCALLRET(context->MapSurfaceToScaledWindow, error, context, &pdu);
+
+ if (error)
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "context->MapSurfaceToScaledWindow failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ size_t end = 0;
+ RDPGFX_HEADER header = { 0 };
+ UINT error = 0;
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+ const size_t beg = Stream_GetPosition(s);
+
+ WINPR_ASSERT(gfx);
+ if ((error = rdpgfx_read_header(s, &header)))
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_header failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ DEBUG_RDPGFX(
+ gfx->log, "cmdId: %s (0x%04" PRIX16 ") flags: 0x%04" PRIX16 " pduLength: %" PRIu32 "",
+ rdpgfx_get_cmd_id_string(header.cmdId), header.cmdId, header.flags, header.pduLength);
+
+ switch (header.cmdId)
+ {
+ case RDPGFX_CMDID_WIRETOSURFACE_1:
+ if ((error = rdpgfx_recv_wire_to_surface_1_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_wire_to_surface_1_pdu failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case RDPGFX_CMDID_WIRETOSURFACE_2:
+ if ((error = rdpgfx_recv_wire_to_surface_2_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_wire_to_surface_2_pdu failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case RDPGFX_CMDID_DELETEENCODINGCONTEXT:
+ if ((error = rdpgfx_recv_delete_encoding_context_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_delete_encoding_context_pdu failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case RDPGFX_CMDID_SOLIDFILL:
+ if ((error = rdpgfx_recv_solid_fill_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_solid_fill_pdu failed with error %" PRIu32 "!", error);
+
+ break;
+
+ case RDPGFX_CMDID_SURFACETOSURFACE:
+ if ((error = rdpgfx_recv_surface_to_surface_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_surface_to_surface_pdu failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case RDPGFX_CMDID_SURFACETOCACHE:
+ if ((error = rdpgfx_recv_surface_to_cache_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_surface_to_cache_pdu failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case RDPGFX_CMDID_CACHETOSURFACE:
+ if ((error = rdpgfx_recv_cache_to_surface_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_cache_to_surface_pdu failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case RDPGFX_CMDID_EVICTCACHEENTRY:
+ if ((error = rdpgfx_recv_evict_cache_entry_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_evict_cache_entry_pdu failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case RDPGFX_CMDID_CREATESURFACE:
+ if ((error = rdpgfx_recv_create_surface_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_create_surface_pdu failed with error %" PRIu32 "!", error);
+
+ break;
+
+ case RDPGFX_CMDID_DELETESURFACE:
+ if ((error = rdpgfx_recv_delete_surface_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_delete_surface_pdu failed with error %" PRIu32 "!", error);
+
+ break;
+
+ case RDPGFX_CMDID_STARTFRAME:
+ if ((error = rdpgfx_recv_start_frame_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_start_frame_pdu failed with error %" PRIu32 "!", error);
+
+ break;
+
+ case RDPGFX_CMDID_ENDFRAME:
+ if ((error = rdpgfx_recv_end_frame_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_end_frame_pdu failed with error %" PRIu32 "!", error);
+
+ break;
+
+ case RDPGFX_CMDID_RESETGRAPHICS:
+ if ((error = rdpgfx_recv_reset_graphics_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_reset_graphics_pdu failed with error %" PRIu32 "!", error);
+
+ break;
+
+ case RDPGFX_CMDID_MAPSURFACETOOUTPUT:
+ if ((error = rdpgfx_recv_map_surface_to_output_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_map_surface_to_output_pdu failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case RDPGFX_CMDID_CACHEIMPORTREPLY:
+ if ((error = rdpgfx_recv_cache_import_reply_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_cache_import_reply_pdu failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case RDPGFX_CMDID_CAPSCONFIRM:
+ if ((error = rdpgfx_recv_caps_confirm_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_caps_confirm_pdu failed with error %" PRIu32 "!", error);
+
+ if ((error = rdpgfx_send_cache_offer(gfx)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_send_cache_offer failed with error %" PRIu32 "!", error);
+
+ break;
+
+ case RDPGFX_CMDID_MAPSURFACETOWINDOW:
+ if ((error = rdpgfx_recv_map_surface_to_window_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_map_surface_to_window_pdu failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case RDPGFX_CMDID_MAPSURFACETOSCALEDWINDOW:
+ if ((error = rdpgfx_recv_map_surface_to_scaled_window_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_map_surface_to_scaled_window_pdu failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ case RDPGFX_CMDID_MAPSURFACETOSCALEDOUTPUT:
+ if ((error = rdpgfx_recv_map_surface_to_scaled_output_pdu(callback, s)))
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_recv_map_surface_to_scaled_output_pdu failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ default:
+ error = CHANNEL_RC_BAD_PROC;
+ break;
+ }
+
+ if (error)
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "Error while processing GFX cmdId: %s (0x%04" PRIX16 ")",
+ rdpgfx_get_cmd_id_string(header.cmdId), header.cmdId);
+ Stream_SetPosition(s, (beg + header.pduLength));
+ return error;
+ }
+
+ end = Stream_GetPosition(s);
+
+ if (end != (beg + header.pduLength))
+ {
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "Unexpected gfx pdu end: Actual: %" PRIuz ", Expected: %" PRIuz, end,
+ (beg + header.pduLength));
+ Stream_SetPosition(s, (beg + header.pduLength));
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
+{
+ wStream* s = NULL;
+ int status = 0;
+ UINT32 DstSize = 0;
+ BYTE* pDstData = NULL;
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(gfx);
+ status = zgfx_decompress(gfx->zgfx, Stream_ConstPointer(data),
+ (UINT32)Stream_GetRemainingLength(data), &pDstData, &DstSize, 0);
+
+ if (status < 0)
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "zgfx_decompress failure! status: %d", status);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ s = Stream_New(pDstData, DstSize);
+
+ if (!s)
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ while (Stream_GetPosition(s) < Stream_Length(s))
+ {
+ if ((error = rdpgfx_recv_pdu(callback, s)))
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_pdu failed with error %" PRIu32 "!",
+ error);
+ break;
+ }
+ }
+
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_on_open(IWTSVirtualChannelCallback* pChannelCallback)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+ UINT error = CHANNEL_RC_OK;
+ BOOL do_caps_advertise = TRUE;
+ gfx->sendFrameAcks = TRUE;
+
+ if (context)
+ {
+ IFCALLRET(context->OnOpen, error, context, &do_caps_advertise, &gfx->sendFrameAcks);
+
+ if (error)
+ WLog_Print(gfx->log, WLOG_ERROR, "context->OnOpen failed with error %" PRIu32 "",
+ error);
+ }
+
+ if (do_caps_advertise)
+ error = rdpgfx_send_supported_caps(callback);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_on_close(IWTSVirtualChannelCallback* pChannelCallback)
+{
+ UINT error = CHANNEL_RC_OK;
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+ WINPR_ASSERT(callback);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin;
+
+ if (!gfx)
+ goto fail;
+
+ RdpgfxClientContext* context = gfx->context;
+
+ DEBUG_RDPGFX(gfx->log, "OnClose");
+ error = rdpgfx_save_persistent_cache(gfx);
+
+ if (error)
+ {
+ // print error, but don't consider this a hard failure
+ WLog_Print(gfx->log, WLOG_ERROR,
+ "rdpgfx_save_persistent_cache failed with error %" PRIu32 "", error);
+ }
+
+ free_surfaces(context, gfx->SurfaceTable);
+ evict_cache_slots(context, gfx->MaxCacheSlots, gfx->CacheSlots);
+
+ free(callback);
+ gfx->UnacknowledgedFrames = 0;
+ gfx->TotalDecodedFrames = 0;
+
+ if (context)
+ {
+ IFCALL(context->OnClose, context);
+ }
+
+fail:
+ return CHANNEL_RC_OK;
+}
+
+static void terminate_plugin_cb(GENERIC_DYNVC_PLUGIN* base)
+{
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)base;
+ WINPR_ASSERT(gfx);
+ RdpgfxClientContext* context = gfx->context;
+
+ DEBUG_RDPGFX(gfx->log, "Terminated");
+ rdpgfx_client_context_free(context);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_set_surface_data(RdpgfxClientContext* context, UINT16 surfaceId, void* pData)
+{
+ ULONG_PTR key = 0;
+ WINPR_ASSERT(context);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)context->handle;
+ WINPR_ASSERT(gfx);
+ key = ((ULONG_PTR)surfaceId) + 1;
+
+ if (pData)
+ {
+ if (!HashTable_Insert(gfx->SurfaceTable, (void*)key, pData))
+ return ERROR_BAD_ARGUMENTS;
+ }
+ else
+ HashTable_Remove(gfx->SurfaceTable, (void*)key);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_get_surface_ids(RdpgfxClientContext* context, UINT16** ppSurfaceIds,
+ UINT16* count_out)
+{
+ size_t count = 0;
+ UINT16* pSurfaceIds = NULL;
+ ULONG_PTR* pKeys = NULL;
+ WINPR_ASSERT(context);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)context->handle;
+ WINPR_ASSERT(gfx);
+ count = HashTable_GetKeys(gfx->SurfaceTable, &pKeys);
+
+ WINPR_ASSERT(ppSurfaceIds);
+ WINPR_ASSERT(count_out);
+ if (count < 1)
+ {
+ *count_out = 0;
+ return CHANNEL_RC_OK;
+ }
+
+ pSurfaceIds = (UINT16*)calloc(count, sizeof(UINT16));
+
+ if (!pSurfaceIds)
+ {
+ WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!");
+ free(pKeys);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ for (size_t index = 0; index < count; index++)
+ {
+ pSurfaceIds[index] = (UINT16)(pKeys[index] - 1);
+ }
+
+ free(pKeys);
+ *ppSurfaceIds = pSurfaceIds;
+ *count_out = (UINT16)count;
+ return CHANNEL_RC_OK;
+}
+
+static void* rdpgfx_get_surface_data(RdpgfxClientContext* context, UINT16 surfaceId)
+{
+ ULONG_PTR key = 0;
+ void* pData = NULL;
+ WINPR_ASSERT(context);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)context->handle;
+ WINPR_ASSERT(gfx);
+ key = ((ULONG_PTR)surfaceId) + 1;
+ pData = HashTable_GetItemValue(gfx->SurfaceTable, (void*)key);
+ return pData;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_set_cache_slot_data(RdpgfxClientContext* context, UINT16 cacheSlot, void* pData)
+{
+ WINPR_ASSERT(context);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)context->handle;
+
+ WINPR_ASSERT(gfx);
+ /* Microsoft uses 1-based indexing for the egfx bitmap cache ! */
+ if (cacheSlot == 0 || cacheSlot > gfx->MaxCacheSlots)
+ {
+ WLog_ERR(TAG, "invalid cache slot %" PRIu16 ", must be between 1 and %" PRIu16 "",
+ cacheSlot, gfx->MaxCacheSlots);
+ return ERROR_INVALID_INDEX;
+ }
+
+ gfx->CacheSlots[cacheSlot - 1] = pData;
+ return CHANNEL_RC_OK;
+}
+
+static void* rdpgfx_get_cache_slot_data(RdpgfxClientContext* context, UINT16 cacheSlot)
+{
+ void* pData = NULL;
+ WINPR_ASSERT(context);
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)context->handle;
+ WINPR_ASSERT(gfx);
+ /* Microsoft uses 1-based indexing for the egfx bitmap cache ! */
+ if (cacheSlot == 0 || cacheSlot > gfx->MaxCacheSlots)
+ {
+ WLog_ERR(TAG, "invalid cache slot %" PRIu16 ", must be between 1 and %" PRIu16 "",
+ cacheSlot, gfx->MaxCacheSlots);
+ return NULL;
+ }
+
+ pData = gfx->CacheSlots[cacheSlot - 1];
+ return pData;
+}
+
+static UINT init_plugin_cb(GENERIC_DYNVC_PLUGIN* base, rdpContext* rcontext, rdpSettings* settings)
+{
+ RdpgfxClientContext* context = NULL;
+ RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)base;
+
+ WINPR_ASSERT(base);
+ gfx->rdpcontext = rcontext;
+ gfx->log = WLog_Get(TAG);
+
+ gfx->SurfaceTable = HashTable_New(TRUE);
+ if (!gfx->SurfaceTable)
+ {
+ WLog_ERR(TAG, "HashTable_New for surfaces failed !");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ gfx->MaxCacheSlots =
+ freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxSmallCache) ? 4096 : 25600;
+
+ context = (RdpgfxClientContext*)calloc(1, sizeof(RdpgfxClientContext));
+ if (!context)
+ {
+ WLog_ERR(TAG, "context calloc failed!");
+ HashTable_Free(gfx->SurfaceTable);
+ gfx->SurfaceTable = NULL;
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ gfx->zgfx = zgfx_context_new(FALSE);
+ if (!gfx->zgfx)
+ {
+ WLog_ERR(TAG, "zgfx_context_new failed!");
+ HashTable_Free(gfx->SurfaceTable);
+ gfx->SurfaceTable = NULL;
+ free(context);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ context->handle = (void*)gfx;
+ context->GetSurfaceIds = rdpgfx_get_surface_ids;
+ context->SetSurfaceData = rdpgfx_set_surface_data;
+ context->GetSurfaceData = rdpgfx_get_surface_data;
+ context->SetCacheSlotData = rdpgfx_set_cache_slot_data;
+ context->GetCacheSlotData = rdpgfx_get_cache_slot_data;
+ context->CapsAdvertise = rdpgfx_send_caps_advertise_pdu;
+ context->FrameAcknowledge = rdpgfx_send_frame_acknowledge_pdu;
+ context->CacheImportOffer = rdpgfx_send_cache_import_offer_pdu;
+ context->QoeFrameAcknowledge = rdpgfx_send_qoe_frame_acknowledge_pdu;
+
+ gfx->base.iface.pInterface = (void*)context;
+ gfx->context = context;
+ return CHANNEL_RC_OK;
+}
+
+void rdpgfx_client_context_free(RdpgfxClientContext* context)
+{
+
+ RDPGFX_PLUGIN* gfx = NULL;
+
+ if (!context)
+ return;
+
+ gfx = (RDPGFX_PLUGIN*)context->handle;
+
+ free_surfaces(context, gfx->SurfaceTable);
+ evict_cache_slots(context, gfx->MaxCacheSlots, gfx->CacheSlots);
+
+ if (gfx->zgfx)
+ {
+ zgfx_context_free(gfx->zgfx);
+ gfx->zgfx = NULL;
+ }
+
+ HashTable_Free(gfx->SurfaceTable);
+ free(context);
+}
+
+static const IWTSVirtualChannelCallback rdpgfx_callbacks = { rdpgfx_on_data_received,
+ rdpgfx_on_open, rdpgfx_on_close,
+ NULL };
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT rdpgfx_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
+{
+ return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, RDPGFX_DVC_CHANNEL_NAME,
+ sizeof(RDPGFX_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK),
+ &rdpgfx_callbacks, init_plugin_cb, terminate_plugin_cb);
+}
diff --git a/channels/rdpgfx/client/rdpgfx_main.h b/channels/rdpgfx/client/rdpgfx_main.h
new file mode 100644
index 0000000..41ce4af
--- /dev/null
+++ b/channels/rdpgfx/client/rdpgfx_main.h
@@ -0,0 +1,61 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Graphics Pipeline Extension
+ *
+ * Copyright 2013-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPGFX_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_RDPGFX_CLIENT_MAIN_H
+
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+#include <freerdp/addin.h>
+
+#include <winpr/wlog.h>
+#include <winpr/collections.h>
+
+#include <freerdp/client/channels.h>
+#include <freerdp/client/rdpgfx.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/codec/zgfx.h>
+#include <freerdp/cache/persistent.h>
+#include <freerdp/freerdp.h>
+
+typedef struct
+{
+ GENERIC_DYNVC_PLUGIN base;
+
+ ZGFX_CONTEXT* zgfx;
+ UINT32 UnacknowledgedFrames;
+ UINT32 TotalDecodedFrames;
+ UINT64 StartDecodingTime;
+ BOOL suspendFrameAcks;
+ BOOL sendFrameAcks;
+
+ wHashTable* SurfaceTable;
+
+ UINT16 MaxCacheSlots;
+ void* CacheSlots[25600];
+ rdpPersistentCache* persistent;
+
+ rdpContext* rdpcontext;
+
+ wLog* log;
+ RDPGFX_CAPSET ConnectionCaps;
+ RdpgfxClientContext* context;
+} RDPGFX_PLUGIN;
+
+#endif /* FREERDP_CHANNEL_RDPGFX_CLIENT_MAIN_H */
diff --git a/channels/rdpgfx/rdpgfx_common.c b/channels/rdpgfx/rdpgfx_common.c
new file mode 100644
index 0000000..775641c
--- /dev/null
+++ b/channels/rdpgfx/rdpgfx_common.c
@@ -0,0 +1,197 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Graphics Pipeline Extension
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/stream.h>
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("rdpgfx.common")
+
+#include "rdpgfx_common.h"
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpgfx_read_header(wStream* s, RDPGFX_HEADER* header)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Read_UINT16(s, header->cmdId); /* cmdId (2 bytes) */
+ Stream_Read_UINT16(s, header->flags); /* flags (2 bytes) */
+ Stream_Read_UINT32(s, header->pduLength); /* pduLength (4 bytes) */
+
+ if (header->pduLength < 8)
+ {
+ WLog_ERR(TAG, "header->pduLength %u less than 8!", header->pduLength);
+ return ERROR_INVALID_DATA;
+ }
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (header->pduLength - 8)))
+ return ERROR_INVALID_DATA;
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpgfx_write_header(wStream* s, const RDPGFX_HEADER* header)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return CHANNEL_RC_NO_MEMORY;
+ Stream_Write_UINT16(s, header->cmdId); /* cmdId (2 bytes) */
+ Stream_Write_UINT16(s, header->flags); /* flags (2 bytes) */
+ Stream_Write_UINT32(s, header->pduLength); /* pduLength (4 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpgfx_read_point16(wStream* s, RDPGFX_POINT16* pt16)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(pt16);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pt16->x); /* x (2 bytes) */
+ Stream_Read_UINT16(s, pt16->y); /* y (2 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpgfx_write_point16(wStream* s, const RDPGFX_POINT16* point16)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(point16);
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT16(s, point16->x); /* x (2 bytes) */
+ Stream_Write_UINT16(s, point16->y); /* y (2 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpgfx_read_rect16(wStream* s, RECTANGLE_16* rect16)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(rect16);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, rect16->left); /* left (2 bytes) */
+ Stream_Read_UINT16(s, rect16->top); /* top (2 bytes) */
+ Stream_Read_UINT16(s, rect16->right); /* right (2 bytes) */
+ Stream_Read_UINT16(s, rect16->bottom); /* bottom (2 bytes) */
+ if (rect16->left >= rect16->right)
+ return ERROR_INVALID_DATA;
+ if (rect16->top >= rect16->bottom)
+ return ERROR_INVALID_DATA;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpgfx_write_rect16(wStream* s, const RECTANGLE_16* rect16)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(rect16);
+
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT16(s, rect16->left); /* left (2 bytes) */
+ Stream_Write_UINT16(s, rect16->top); /* top (2 bytes) */
+ Stream_Write_UINT16(s, rect16->right); /* right (2 bytes) */
+ Stream_Write_UINT16(s, rect16->bottom); /* bottom (2 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpgfx_read_color32(wStream* s, RDPGFX_COLOR32* color32)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(color32);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT8(s, color32->B); /* B (1 byte) */
+ Stream_Read_UINT8(s, color32->G); /* G (1 byte) */
+ Stream_Read_UINT8(s, color32->R); /* R (1 byte) */
+ Stream_Read_UINT8(s, color32->XA); /* XA (1 byte) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpgfx_write_color32(wStream* s, const RDPGFX_COLOR32* color32)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(color32);
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT8(s, color32->B); /* B (1 byte) */
+ Stream_Write_UINT8(s, color32->G); /* G (1 byte) */
+ Stream_Write_UINT8(s, color32->R); /* R (1 byte) */
+ Stream_Write_UINT8(s, color32->XA); /* XA (1 byte) */
+ return CHANNEL_RC_OK;
+}
diff --git a/channels/rdpgfx/rdpgfx_common.h b/channels/rdpgfx/rdpgfx_common.h
new file mode 100644
index 0000000..246b6ae
--- /dev/null
+++ b/channels/rdpgfx/rdpgfx_common.h
@@ -0,0 +1,54 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Graphics Pipeline Extension
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPGFX_COMMON_H
+#define FREERDP_CHANNEL_RDPGFX_COMMON_H
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+
+#include <freerdp/config.h>
+#include <freerdp/channels/rdpgfx.h>
+#include <freerdp/api.h>
+#include <freerdp/utils/gfx.h>
+
+FREERDP_LOCAL UINT rdpgfx_read_header(wStream* s, RDPGFX_HEADER* header);
+FREERDP_LOCAL UINT rdpgfx_write_header(wStream* s, const RDPGFX_HEADER* header);
+
+FREERDP_LOCAL UINT rdpgfx_read_point16(wStream* s, RDPGFX_POINT16* pt16);
+FREERDP_LOCAL UINT rdpgfx_write_point16(wStream* s, const RDPGFX_POINT16* point16);
+
+FREERDP_LOCAL UINT rdpgfx_read_rect16(wStream* s, RECTANGLE_16* rect16);
+FREERDP_LOCAL UINT rdpgfx_write_rect16(wStream* s, const RECTANGLE_16* rect16);
+
+FREERDP_LOCAL UINT rdpgfx_read_color32(wStream* s, RDPGFX_COLOR32* color32);
+FREERDP_LOCAL UINT rdpgfx_write_color32(wStream* s, const RDPGFX_COLOR32* color32);
+
+#ifdef WITH_DEBUG_RDPGFX
+#define DEBUG_RDPGFX(_LOGGER, ...) WLog_Print(_LOGGER, WLOG_DEBUG, __VA_ARGS__)
+#else
+#define DEBUG_RDPGFX(_LOGGER, ...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#endif /* FREERDP_CHANNEL_RDPGFX_COMMON_H */
diff --git a/channels/rdpgfx/server/CMakeLists.txt b/channels/rdpgfx/server/CMakeLists.txt
new file mode 100644
index 0000000..d33b710
--- /dev/null
+++ b/channels/rdpgfx/server/CMakeLists.txt
@@ -0,0 +1,33 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2016 Jiang Zihao <zihao.jiang@yahoo.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_server("rdpgfx")
+
+set(${MODULE_PREFIX}_SRCS
+ rdpgfx_main.c
+ rdpgfx_main.h
+ ../rdpgfx_common.c
+ ../rdpgfx_common.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+
+include_directories(..)
+
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry")
diff --git a/channels/rdpgfx/server/rdpgfx_main.c b/channels/rdpgfx/server/rdpgfx_main.c
new file mode 100644
index 0000000..0a3fabc
--- /dev/null
+++ b/channels/rdpgfx/server/rdpgfx_main.c
@@ -0,0 +1,1863 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Graphics Pipeline Extension
+ *
+ * Copyright 2016 Jiang Zihao <zihao.jiang@yahoo.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/codec/color.h>
+
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/log.h>
+
+#include "rdpgfx_common.h"
+#include "rdpgfx_main.h"
+
+#define TAG CHANNELS_TAG("rdpgfx.server")
+#define RDPGFX_RESET_GRAPHICS_PDU_SIZE 340
+
+#define checkCapsAreExchanged(context) \
+ checkCapsAreExchangedInt(context, __FILE__, __func__, __LINE__)
+static BOOL checkCapsAreExchangedInt(RdpgfxServerContext* context, const char* file,
+ const char* fkt, size_t line)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ const DWORD level = WLOG_TRACE;
+ if (WLog_IsLevelActive(context->priv->log, level))
+ {
+ WLog_PrintMessage(context->priv->log, WLOG_MESSAGE_TEXT, level, line, file, fkt,
+ "activeCapSet{Version=0x%08" PRIx32 ", flags=0x%08" PRIx32 "}",
+ context->priv->activeCapSet.version, context->priv->activeCapSet.flags);
+ }
+ return context->priv->activeCapSet.version > 0;
+}
+
+/**
+ * Function description
+ * Calculate packet size from data length.
+ * It would be data length + header.
+ *
+ * @param dataLen estimated data length without header
+ *
+ * @return new stream
+ */
+static INLINE UINT32 rdpgfx_pdu_length(UINT32 dataLen)
+{
+ return RDPGFX_HEADER_SIZE + dataLen;
+}
+
+static INLINE UINT rdpgfx_server_packet_init_header(wStream* s, UINT16 cmdId, UINT32 pduLength)
+{
+ RDPGFX_HEADER header;
+ header.flags = 0;
+ header.cmdId = cmdId;
+ header.pduLength = pduLength;
+ /* Write header. Note that actual length might be changed
+ * after the entire packet has been constructed. */
+ return rdpgfx_write_header(s, &header);
+}
+
+/**
+ * Function description
+ * Complete the rdpgfx packet header.
+ *
+ * @param s stream
+ * @param start saved start pos of the packet in the stream
+ */
+static INLINE BOOL rdpgfx_server_packet_complete_header(wStream* s, size_t start)
+{
+ const size_t current = Stream_GetPosition(s);
+ const size_t cap = Stream_Capacity(s);
+ if (cap < start + RDPGFX_HEADER_SIZE)
+ return FALSE;
+ /* Fill actual length */
+ Stream_SetPosition(s, start + RDPGFX_HEADER_SIZE - sizeof(UINT32));
+ Stream_Write_UINT32(s, current - start); /* pduLength (4 bytes) */
+ Stream_SetPosition(s, current);
+ return TRUE;
+}
+
+/**
+ * Function description
+ * Send the stream for rdpgfx server packet.
+ * The packet would be compressed according to [MS-RDPEGFX].
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_server_packet_send(RdpgfxServerContext* context, wStream* s)
+{
+ UINT error = 0;
+ UINT32 flags = 0;
+ ULONG written = 0;
+ BYTE* pSrcData = Stream_Buffer(s);
+ UINT32 SrcSize = Stream_GetPosition(s);
+ wStream* fs = NULL;
+ /* Allocate new stream with enough capacity. Additional overhead is
+ * descriptor (1 bytes) + segmentCount (2 bytes) + uncompressedSize (4 bytes)
+ * + segmentCount * size (4 bytes) */
+ fs = Stream_New(NULL, SrcSize + 7 + (SrcSize / ZGFX_SEGMENTED_MAXSIZE + 1) * 4);
+
+ if (!fs)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ if (zgfx_compress_to_stream(context->priv->zgfx, fs, pSrcData, SrcSize, &flags) < 0)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "zgfx_compress_to_stream failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ if (!WTSVirtualChannelWrite(context->priv->rdpgfx_channel, (PCHAR)Stream_Buffer(fs),
+ Stream_GetPosition(fs), &written))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelWrite failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ if (written < Stream_GetPosition(fs))
+ {
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written,
+ Stream_GetPosition(fs));
+ }
+
+ error = CHANNEL_RC_OK;
+out:
+ Stream_Free(fs, TRUE);
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ * Create new stream for single rdpgfx packet. The new stream length
+ * would be required data length + header. The header will be written
+ * to the stream before return, but the pduLength field might be
+ * changed in rdpgfx_server_single_packet_send.
+ *
+ * @param cmdId The CommandID to write
+ * @param dataLen estimated data length without header
+ *
+ * @return new stream
+ */
+static wStream* rdpgfx_server_single_packet_new(wLog* log, UINT16 cmdId, UINT32 dataLen)
+{
+ UINT error = 0;
+ wStream* s = NULL;
+ UINT32 pduLength = rdpgfx_pdu_length(dataLen);
+ s = Stream_New(NULL, pduLength);
+
+ if (!s)
+ {
+ WLog_Print(log, WLOG_ERROR, "Stream_New failed!");
+ goto error;
+ }
+
+ if ((error = rdpgfx_server_packet_init_header(s, cmdId, pduLength)))
+ {
+ WLog_Print(log, WLOG_ERROR, "Failed to init header with error %" PRIu32 "!", error);
+ goto error;
+ }
+
+ return s;
+error:
+ Stream_Free(s, TRUE);
+ return NULL;
+}
+
+/**
+ * Function description
+ * Send the stream for single rdpgfx packet.
+ * The header will be filled with actual length.
+ * The packet would be compressed according to [MS-RDPEGFX].
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static INLINE UINT rdpgfx_server_single_packet_send(RdpgfxServerContext* context, wStream* s)
+{
+ /* Fill actual length */
+ rdpgfx_server_packet_complete_header(s, 0);
+ return rdpgfx_server_packet_send(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_caps_confirm_pdu(RdpgfxServerContext* context,
+ const RDPGFX_CAPS_CONFIRM_PDU* capsConfirm)
+{
+ wStream* s = NULL;
+ RDPGFX_CAPSET* capsSet = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(capsConfirm);
+
+ capsSet = capsConfirm->capsSet;
+ WINPR_ASSERT(capsSet);
+
+ s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_CAPSCONFIRM,
+ RDPGFX_CAPSET_BASE_SIZE + capsSet->length);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ WLog_DBG(TAG, "CAPS version=0x%04" PRIx32 ", flags=0x%04" PRIx32 ", length=%" PRIu32,
+ capsSet->version, capsSet->flags, capsSet->length);
+ Stream_Write_UINT32(s, capsSet->version); /* version (4 bytes) */
+ Stream_Write_UINT32(s, capsSet->length); /* capsDataLength (4 bytes) */
+
+ if (capsSet->length >= 4)
+ {
+ Stream_Write_UINT32(s, capsSet->flags); /* capsData (4 bytes) */
+ Stream_Zero(s, capsSet->length - 4);
+ }
+ else
+ Stream_Zero(s, capsSet->length);
+
+ context->priv->activeCapSet = *capsSet;
+ return rdpgfx_server_single_packet_send(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_reset_graphics_pdu(RdpgfxServerContext* context,
+ const RDPGFX_RESET_GRAPHICS_PDU* pdu)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+
+ wStream* s = NULL;
+
+ /* Check monitorCount. This ensures total size within 340 bytes) */
+ if (pdu->monitorCount >= 16)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "Monitor count MUST be less than or equal to 16: %" PRIu32 "",
+ pdu->monitorCount);
+ return ERROR_INVALID_DATA;
+ }
+
+ s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_RESETGRAPHICS,
+ RDPGFX_RESET_GRAPHICS_PDU_SIZE - RDPGFX_HEADER_SIZE);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT32(s, pdu->width); /* width (4 bytes) */
+ Stream_Write_UINT32(s, pdu->height); /* height (4 bytes) */
+ Stream_Write_UINT32(s, pdu->monitorCount); /* monitorCount (4 bytes) */
+
+ for (UINT32 index = 0; index < pdu->monitorCount; index++)
+ {
+ const MONITOR_DEF* monitor = &(pdu->monitorDefArray[index]);
+ Stream_Write_UINT32(s, monitor->left); /* left (4 bytes) */
+ Stream_Write_UINT32(s, monitor->top); /* top (4 bytes) */
+ Stream_Write_UINT32(s, monitor->right); /* right (4 bytes) */
+ Stream_Write_UINT32(s, monitor->bottom); /* bottom (4 bytes) */
+ Stream_Write_UINT32(s, monitor->flags); /* flags (4 bytes) */
+ }
+
+ /* pad (total size must be 340 bytes) */
+ Stream_SetPosition(s, RDPGFX_RESET_GRAPHICS_PDU_SIZE);
+ return rdpgfx_server_single_packet_send(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_evict_cache_entry_pdu(RdpgfxServerContext* context,
+ const RDPGFX_EVICT_CACHE_ENTRY_PDU* pdu)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ wStream* s =
+ rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_EVICTCACHEENTRY, 2);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, pdu->cacheSlot); /* cacheSlot (2 bytes) */
+ return rdpgfx_server_single_packet_send(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_cache_import_reply_pdu(RdpgfxServerContext* context,
+ const RDPGFX_CACHE_IMPORT_REPLY_PDU* pdu)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(pdu);
+
+ WLog_DBG(TAG, "reply with %" PRIu16 " entries", pdu->importedEntriesCount);
+ wStream* s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_CACHEIMPORTREPLY,
+ 2 + 2 * pdu->importedEntriesCount);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ /* importedEntriesCount (2 bytes) */
+ Stream_Write_UINT16(s, pdu->importedEntriesCount);
+
+ for (UINT16 index = 0; index < pdu->importedEntriesCount; index++)
+ {
+ Stream_Write_UINT16(s, pdu->cacheSlots[index]); /* cacheSlot (2 bytes) */
+ }
+
+ return rdpgfx_server_single_packet_send(context, s);
+}
+
+static UINT
+rdpgfx_process_cache_import_offer_pdu(RdpgfxServerContext* context,
+ const RDPGFX_CACHE_IMPORT_OFFER_PDU* cacheImportOffer)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cacheImportOffer);
+
+ RDPGFX_CACHE_IMPORT_REPLY_PDU reply = { 0 };
+ WLog_DBG(TAG, "received %" PRIu16 " entries, reply with %" PRIu16 " entries",
+ cacheImportOffer->cacheEntriesCount, reply.importedEntriesCount);
+ return IFCALLRESULT(CHANNEL_RC_OK, context->CacheImportReply, context, &reply);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_create_surface_pdu(RdpgfxServerContext* context,
+ const RDPGFX_CREATE_SURFACE_PDU* pdu)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ wStream* s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_CREATESURFACE, 7);
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(pdu);
+ WINPR_ASSERT((pdu->pixelFormat == GFX_PIXEL_FORMAT_XRGB_8888) ||
+ (pdu->pixelFormat == GFX_PIXEL_FORMAT_ARGB_8888));
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */
+ Stream_Write_UINT16(s, pdu->width); /* width (2 bytes) */
+ Stream_Write_UINT16(s, pdu->height); /* height (2 bytes) */
+ Stream_Write_UINT8(s, pdu->pixelFormat); /* RDPGFX_PIXELFORMAT (1 byte) */
+ return rdpgfx_server_single_packet_send(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_delete_surface_pdu(RdpgfxServerContext* context,
+ const RDPGFX_DELETE_SURFACE_PDU* pdu)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ wStream* s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_DELETESURFACE, 2);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */
+ return rdpgfx_server_single_packet_send(context, s);
+}
+
+static INLINE BOOL rdpgfx_write_start_frame_pdu(wStream* s, const RDPGFX_START_FRAME_PDU* pdu)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return FALSE;
+ Stream_Write_UINT32(s, pdu->timestamp); /* timestamp (4 bytes) */
+ Stream_Write_UINT32(s, pdu->frameId); /* frameId (4 bytes) */
+ return TRUE;
+}
+
+static INLINE BOOL rdpgfx_write_end_frame_pdu(wStream* s, const RDPGFX_END_FRAME_PDU* pdu)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return FALSE;
+ Stream_Write_UINT32(s, pdu->frameId); /* frameId (4 bytes) */
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_start_frame_pdu(RdpgfxServerContext* context,
+ const RDPGFX_START_FRAME_PDU* pdu)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ wStream* s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_STARTFRAME,
+ RDPGFX_START_FRAME_PDU_SIZE);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rdpgfx_write_start_frame_pdu(s, pdu);
+ return rdpgfx_server_single_packet_send(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_end_frame_pdu(RdpgfxServerContext* context, const RDPGFX_END_FRAME_PDU* pdu)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ wStream* s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_ENDFRAME,
+ RDPGFX_END_FRAME_PDU_SIZE);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rdpgfx_write_end_frame_pdu(s, pdu);
+ return rdpgfx_server_single_packet_send(context, s);
+}
+
+/**
+ * Function description
+ * Estimate RFX_AVC420_BITMAP_STREAM structure size in stream
+ *
+ * @return estimated size
+ */
+static INLINE UINT32 rdpgfx_estimate_h264_avc420(const RDPGFX_AVC420_BITMAP_STREAM* havc420)
+{
+ /* H264 metadata + H264 stream. See rdpgfx_write_h264_avc420 */
+ return sizeof(UINT32) /* numRegionRects */
+ + 10 /* regionRects + quantQualityVals */
+ * havc420->meta.numRegionRects +
+ havc420->length;
+}
+
+/**
+ * Function description
+ * Estimate surface command packet size in stream without header
+ *
+ * @return estimated size
+ */
+static INLINE UINT32 rdpgfx_estimate_surface_command(const RDPGFX_SURFACE_COMMAND* cmd)
+{
+ RDPGFX_AVC420_BITMAP_STREAM* havc420 = NULL;
+ RDPGFX_AVC444_BITMAP_STREAM* havc444 = NULL;
+ UINT32 h264Size = 0;
+
+ /* Estimate stream size according to codec. */
+ switch (cmd->codecId)
+ {
+ case RDPGFX_CODECID_CAPROGRESSIVE:
+ case RDPGFX_CODECID_CAPROGRESSIVE_V2:
+ return RDPGFX_WIRE_TO_SURFACE_PDU_2_SIZE + cmd->length;
+
+ case RDPGFX_CODECID_AVC420:
+ havc420 = (RDPGFX_AVC420_BITMAP_STREAM*)cmd->extra;
+ h264Size = rdpgfx_estimate_h264_avc420(havc420);
+ return RDPGFX_WIRE_TO_SURFACE_PDU_1_SIZE + h264Size;
+
+ case RDPGFX_CODECID_AVC444:
+ havc444 = (RDPGFX_AVC444_BITMAP_STREAM*)cmd->extra;
+ h264Size = sizeof(UINT32); /* cbAvc420EncodedBitstream1 */
+ /* avc420EncodedBitstream1 */
+ havc420 = &(havc444->bitstream[0]);
+ h264Size += rdpgfx_estimate_h264_avc420(havc420);
+
+ /* avc420EncodedBitstream2 */
+ if (havc444->LC == 0)
+ {
+ havc420 = &(havc444->bitstream[1]);
+ h264Size += rdpgfx_estimate_h264_avc420(havc420);
+ }
+
+ return RDPGFX_WIRE_TO_SURFACE_PDU_1_SIZE + h264Size;
+
+ default:
+ return RDPGFX_WIRE_TO_SURFACE_PDU_1_SIZE + cmd->length;
+ }
+}
+
+/**
+ * Function description
+ * Resolve RDPGFX_CMDID_WIRETOSURFACE_1 or RDPGFX_CMDID_WIRETOSURFACE_2
+ * according to codecId
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static INLINE UINT16 rdpgfx_surface_command_cmdid(const RDPGFX_SURFACE_COMMAND* cmd)
+{
+ if (cmd->codecId == RDPGFX_CODECID_CAPROGRESSIVE ||
+ cmd->codecId == RDPGFX_CODECID_CAPROGRESSIVE_V2)
+ {
+ return RDPGFX_CMDID_WIRETOSURFACE_2;
+ }
+
+ return RDPGFX_CMDID_WIRETOSURFACE_1;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_write_h264_metablock(wLog* log, wStream* s, const RDPGFX_H264_METABLOCK* meta)
+{
+ RECTANGLE_16* regionRect = NULL;
+ RDPGFX_H264_QUANT_QUALITY* quantQualityVal = NULL;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4 + meta->numRegionRects * 10))
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write_UINT32(s, meta->numRegionRects); /* numRegionRects (4 bytes) */
+
+ for (UINT32 index = 0; index < meta->numRegionRects; index++)
+ {
+ regionRect = &(meta->regionRects[index]);
+
+ if ((error = rdpgfx_write_rect16(s, regionRect)))
+ {
+ WLog_Print(log, WLOG_ERROR, "rdpgfx_write_rect16 failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+ }
+
+ for (UINT32 index = 0; index < meta->numRegionRects; index++)
+ {
+ quantQualityVal = &(meta->quantQualityVals[index]);
+ Stream_Write_UINT8(s, quantQualityVal->qp | (quantQualityVal->r << 6) |
+ (quantQualityVal->p << 7)); /* qpVal (1 byte) */
+ /* qualityVal (1 byte) */
+ Stream_Write_UINT8(s, quantQualityVal->qualityVal);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ * Write RFX_AVC420_BITMAP_STREAM structure to stream
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static INLINE UINT rdpgfx_write_h264_avc420(wLog* log, wStream* s,
+ RDPGFX_AVC420_BITMAP_STREAM* havc420)
+{
+ UINT error = CHANNEL_RC_OK;
+
+ if ((error = rdpgfx_write_h264_metablock(log, s, &(havc420->meta))))
+ {
+ WLog_Print(log, WLOG_ERROR, "rdpgfx_write_h264_metablock failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, havc420->length))
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write(s, havc420->data, havc420->length);
+ return error;
+}
+
+/**
+ * Function description
+ * Write RDPGFX_CMDID_WIRETOSURFACE_1 or RDPGFX_CMDID_WIRETOSURFACE_2
+ * to the stream according to RDPGFX_SURFACE_COMMAND message
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_write_surface_command(wLog* log, wStream* s, const RDPGFX_SURFACE_COMMAND* cmd)
+{
+ UINT error = CHANNEL_RC_OK;
+ RDPGFX_AVC420_BITMAP_STREAM* havc420 = NULL;
+ RDPGFX_AVC444_BITMAP_STREAM* havc444 = NULL;
+ UINT32 bitmapDataStart = 0;
+ UINT32 bitmapDataLength = 0;
+ UINT8 pixelFormat = 0;
+
+ switch (cmd->format)
+ {
+ case PIXEL_FORMAT_BGRX32:
+ pixelFormat = GFX_PIXEL_FORMAT_XRGB_8888;
+ break;
+
+ case PIXEL_FORMAT_BGRA32:
+ pixelFormat = GFX_PIXEL_FORMAT_ARGB_8888;
+ break;
+
+ default:
+ WLog_Print(log, WLOG_ERROR, "Format %s not supported!",
+ FreeRDPGetColorFormatName(cmd->format));
+ return ERROR_INVALID_DATA;
+ }
+
+ if (cmd->codecId == RDPGFX_CODECID_CAPROGRESSIVE ||
+ cmd->codecId == RDPGFX_CODECID_CAPROGRESSIVE_V2)
+ {
+ if (!Stream_EnsureRemainingCapacity(s, 13 + cmd->length))
+ return ERROR_INTERNAL_ERROR;
+ /* Write RDPGFX_CMDID_WIRETOSURFACE_2 format for CAPROGRESSIVE */
+ Stream_Write_UINT16(s, cmd->surfaceId); /* surfaceId (2 bytes) */
+ Stream_Write_UINT16(s, cmd->codecId); /* codecId (2 bytes) */
+ Stream_Write_UINT32(s, cmd->contextId); /* codecContextId (4 bytes) */
+ Stream_Write_UINT8(s, pixelFormat); /* pixelFormat (1 byte) */
+ Stream_Write_UINT32(s, cmd->length); /* bitmapDataLength (4 bytes) */
+ Stream_Write(s, cmd->data, cmd->length);
+ }
+ else
+ {
+ /* Write RDPGFX_CMDID_WIRETOSURFACE_1 format for others */
+ if (!Stream_EnsureRemainingCapacity(s, 17))
+ return ERROR_INTERNAL_ERROR;
+ Stream_Write_UINT16(s, cmd->surfaceId); /* surfaceId (2 bytes) */
+ Stream_Write_UINT16(s, cmd->codecId); /* codecId (2 bytes) */
+ Stream_Write_UINT8(s, pixelFormat); /* pixelFormat (1 byte) */
+ Stream_Write_UINT16(s, cmd->left); /* left (2 bytes) */
+ Stream_Write_UINT16(s, cmd->top); /* top (2 bytes) */
+ Stream_Write_UINT16(s, cmd->right); /* right (2 bytes) */
+ Stream_Write_UINT16(s, cmd->bottom); /* bottom (2 bytes) */
+ Stream_Write_UINT32(s, cmd->length); /* bitmapDataLength (4 bytes) */
+ bitmapDataStart = Stream_GetPosition(s);
+
+ if (cmd->codecId == RDPGFX_CODECID_AVC420)
+ {
+ havc420 = (RDPGFX_AVC420_BITMAP_STREAM*)cmd->extra;
+ error = rdpgfx_write_h264_avc420(log, s, havc420);
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(log, WLOG_ERROR, "rdpgfx_write_h264_avc420 failed!");
+ return error;
+ }
+ }
+ else if ((cmd->codecId == RDPGFX_CODECID_AVC444) ||
+ (cmd->codecId == RDPGFX_CODECID_AVC444v2))
+ {
+ havc444 = (RDPGFX_AVC444_BITMAP_STREAM*)cmd->extra;
+ havc420 = &(havc444->bitstream[0]); /* avc420EncodedBitstreamInfo (4 bytes) */
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return ERROR_INTERNAL_ERROR;
+ Stream_Write_UINT32(s, havc444->cbAvc420EncodedBitstream1 | (havc444->LC << 30UL));
+ /* avc420EncodedBitstream1 */
+ error = rdpgfx_write_h264_avc420(log, s, havc420);
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(log, WLOG_ERROR, "rdpgfx_write_h264_avc420 failed!");
+ return error;
+ }
+
+ /* avc420EncodedBitstream2 */
+ if (havc444->LC == 0)
+ {
+ havc420 = &(havc444->bitstream[1]);
+ error = rdpgfx_write_h264_avc420(log, s, havc420);
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(log, WLOG_ERROR, "rdpgfx_write_h264_avc420 failed!");
+ return error;
+ }
+ }
+ }
+ else
+ {
+ if (!Stream_EnsureRemainingCapacity(s, cmd->length))
+ return ERROR_INTERNAL_ERROR;
+ Stream_Write(s, cmd->data, cmd->length);
+ }
+
+ /* Fill actual bitmap data length */
+ bitmapDataLength = Stream_GetPosition(s) - bitmapDataStart;
+ Stream_SetPosition(s, bitmapDataStart - sizeof(UINT32));
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return ERROR_INTERNAL_ERROR;
+ Stream_Write_UINT32(s, bitmapDataLength); /* bitmapDataLength (4 bytes) */
+ if (!Stream_SafeSeek(s, bitmapDataLength))
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ * Send RDPGFX_CMDID_WIRETOSURFACE_1 or RDPGFX_CMDID_WIRETOSURFACE_2
+ * message according to codecId
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_surface_command(RdpgfxServerContext* context,
+ const RDPGFX_SURFACE_COMMAND* cmd)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ UINT error = CHANNEL_RC_OK;
+ wStream* s = NULL;
+ s = rdpgfx_server_single_packet_new(context->priv->log, rdpgfx_surface_command_cmdid(cmd),
+ rdpgfx_estimate_surface_command(cmd));
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ error = rdpgfx_write_surface_command(context->priv->log, s, cmd);
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_write_surface_command failed!");
+ goto error;
+ }
+
+ return rdpgfx_server_single_packet_send(context, s);
+error:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ * Send RDPGFX_CMDID_WIRETOSURFACE_1 or RDPGFX_CMDID_WIRETOSURFACE_2
+ * message according to codecId.
+ * Prepend/append start/end frame message in same packet if exists.
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_surface_frame_command(RdpgfxServerContext* context,
+ const RDPGFX_SURFACE_COMMAND* cmd,
+ const RDPGFX_START_FRAME_PDU* startFrame,
+ const RDPGFX_END_FRAME_PDU* endFrame)
+
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ UINT error = CHANNEL_RC_OK;
+ wStream* s = NULL;
+ UINT32 position = 0;
+ UINT32 size = rdpgfx_pdu_length(rdpgfx_estimate_surface_command(cmd));
+
+ if (startFrame)
+ {
+ size += rdpgfx_pdu_length(RDPGFX_START_FRAME_PDU_SIZE);
+ }
+
+ if (endFrame)
+ {
+ size += rdpgfx_pdu_length(RDPGFX_END_FRAME_PDU_SIZE);
+ }
+
+ s = Stream_New(NULL, size);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ /* Write start frame if exists */
+ if (startFrame)
+ {
+ position = Stream_GetPosition(s);
+ error = rdpgfx_server_packet_init_header(s, RDPGFX_CMDID_STARTFRAME, 0);
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "Failed to init header with error %" PRIu32 "!", error);
+ goto error;
+ }
+
+ if (!rdpgfx_write_start_frame_pdu(s, startFrame) ||
+ !rdpgfx_server_packet_complete_header(s, position))
+ goto error;
+ }
+
+ /* Write RDPGFX_CMDID_WIRETOSURFACE_1 or RDPGFX_CMDID_WIRETOSURFACE_2 */
+ position = Stream_GetPosition(s);
+ error = rdpgfx_server_packet_init_header(s, rdpgfx_surface_command_cmdid(cmd),
+ 0); // Actual length will be filled later
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Failed to init header with error %" PRIu32 "!",
+ error);
+ goto error;
+ }
+
+ error = rdpgfx_write_surface_command(context->priv->log, s, cmd);
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_write_surface_command failed!");
+ goto error;
+ }
+
+ if (!rdpgfx_server_packet_complete_header(s, position))
+ goto error;
+
+ /* Write end frame if exists */
+ if (endFrame)
+ {
+ position = Stream_GetPosition(s);
+ error = rdpgfx_server_packet_init_header(s, RDPGFX_CMDID_ENDFRAME, 0);
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "Failed to init header with error %" PRIu32 "!", error);
+ goto error;
+ }
+
+ if (!rdpgfx_write_end_frame_pdu(s, endFrame) ||
+ !rdpgfx_server_packet_complete_header(s, position))
+ goto error;
+ }
+
+ return rdpgfx_server_packet_send(context, s);
+error:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_delete_encoding_context_pdu(RdpgfxServerContext* context,
+ const RDPGFX_DELETE_ENCODING_CONTEXT_PDU* pdu)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ wStream* s =
+ rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_DELETEENCODINGCONTEXT, 6);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */
+ Stream_Write_UINT32(s, pdu->codecContextId); /* codecContextId (4 bytes) */
+ return rdpgfx_server_single_packet_send(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_solid_fill_pdu(RdpgfxServerContext* context,
+ const RDPGFX_SOLID_FILL_PDU* pdu)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ UINT error = CHANNEL_RC_OK;
+ RECTANGLE_16* fillRect = NULL;
+ wStream* s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_SOLIDFILL,
+ 8 + 8 * pdu->fillRectCount);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */
+
+ /* fillPixel (4 bytes) */
+ if ((error = rdpgfx_write_color32(s, &(pdu->fillPixel))))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpgfx_write_color32 failed with error %" PRIu32 "!", error);
+ goto error;
+ }
+
+ Stream_Write_UINT16(s, pdu->fillRectCount); /* fillRectCount (2 bytes) */
+
+ for (UINT16 index = 0; index < pdu->fillRectCount; index++)
+ {
+ fillRect = &(pdu->fillRects[index]);
+
+ if ((error = rdpgfx_write_rect16(s, fillRect)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpgfx_write_rect16 failed with error %" PRIu32 "!", error);
+ goto error;
+ }
+ }
+
+ return rdpgfx_server_single_packet_send(context, s);
+error:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_surface_to_surface_pdu(RdpgfxServerContext* context,
+ const RDPGFX_SURFACE_TO_SURFACE_PDU* pdu)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ UINT error = CHANNEL_RC_OK;
+ RDPGFX_POINT16* destPt = NULL;
+ wStream* s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_SURFACETOSURFACE,
+ 14 + 4 * pdu->destPtsCount);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, pdu->surfaceIdSrc); /* surfaceIdSrc (2 bytes) */
+ Stream_Write_UINT16(s, pdu->surfaceIdDest); /* surfaceIdDest (2 bytes) */
+
+ /* rectSrc (8 bytes ) */
+ if ((error = rdpgfx_write_rect16(s, &(pdu->rectSrc))))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpgfx_write_rect16 failed with error %" PRIu32 "!", error);
+ goto error;
+ }
+
+ Stream_Write_UINT16(s, pdu->destPtsCount); /* destPtsCount (2 bytes) */
+
+ for (UINT16 index = 0; index < pdu->destPtsCount; index++)
+ {
+ destPt = &(pdu->destPts[index]);
+
+ if ((error = rdpgfx_write_point16(s, destPt)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpgfx_write_point16 failed with error %" PRIu32 "!", error);
+ goto error;
+ }
+ }
+
+ return rdpgfx_server_single_packet_send(context, s);
+error:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_surface_to_cache_pdu(RdpgfxServerContext* context,
+ const RDPGFX_SURFACE_TO_CACHE_PDU* pdu)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ UINT error = CHANNEL_RC_OK;
+ wStream* s =
+ rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_SURFACETOCACHE, 20);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */
+ Stream_Write_UINT64(s, pdu->cacheKey); /* cacheKey (8 bytes) */
+ Stream_Write_UINT16(s, pdu->cacheSlot); /* cacheSlot (2 bytes) */
+
+ /* rectSrc (8 bytes ) */
+ if ((error = rdpgfx_write_rect16(s, &(pdu->rectSrc))))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpgfx_write_rect16 failed with error %" PRIu32 "!", error);
+ goto error;
+ }
+
+ return rdpgfx_server_single_packet_send(context, s);
+error:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_cache_to_surface_pdu(RdpgfxServerContext* context,
+ const RDPGFX_CACHE_TO_SURFACE_PDU* pdu)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ UINT error = CHANNEL_RC_OK;
+ RDPGFX_POINT16* destPt = NULL;
+ wStream* s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_CACHETOSURFACE,
+ 6 + 4 * pdu->destPtsCount);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, pdu->cacheSlot); /* cacheSlot (2 bytes) */
+ Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */
+ Stream_Write_UINT16(s, pdu->destPtsCount); /* destPtsCount (2 bytes) */
+
+ for (UINT16 index = 0; index < pdu->destPtsCount; index++)
+ {
+ destPt = &(pdu->destPts[index]);
+
+ if ((error = rdpgfx_write_point16(s, destPt)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpgfx_write_point16 failed with error %" PRIu32 "", error);
+ goto error;
+ }
+ }
+
+ return rdpgfx_server_single_packet_send(context, s);
+error:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_map_surface_to_output_pdu(RdpgfxServerContext* context,
+ const RDPGFX_MAP_SURFACE_TO_OUTPUT_PDU* pdu)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ wStream* s =
+ rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_MAPSURFACETOOUTPUT, 12);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* reserved (2 bytes). Must be 0 */
+ Stream_Write_UINT32(s, pdu->outputOriginX); /* outputOriginX (4 bytes) */
+ Stream_Write_UINT32(s, pdu->outputOriginY); /* outputOriginY (4 bytes) */
+ return rdpgfx_server_single_packet_send(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_send_map_surface_to_window_pdu(RdpgfxServerContext* context,
+ const RDPGFX_MAP_SURFACE_TO_WINDOW_PDU* pdu)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ wStream* s =
+ rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_MAPSURFACETOWINDOW, 18);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */
+ Stream_Write_UINT64(s, pdu->windowId); /* windowId (8 bytes) */
+ Stream_Write_UINT32(s, pdu->mappedWidth); /* mappedWidth (4 bytes) */
+ Stream_Write_UINT32(s, pdu->mappedHeight); /* mappedHeight (4 bytes) */
+ return rdpgfx_server_single_packet_send(context, s);
+}
+
+static UINT
+rdpgfx_send_map_surface_to_scaled_window_pdu(RdpgfxServerContext* context,
+ const RDPGFX_MAP_SURFACE_TO_SCALED_WINDOW_PDU* pdu)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ wStream* s = rdpgfx_server_single_packet_new(context->priv->log,
+ RDPGFX_CMDID_MAPSURFACETOSCALEDWINDOW, 26);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */
+ Stream_Write_UINT64(s, pdu->windowId); /* windowId (8 bytes) */
+ Stream_Write_UINT32(s, pdu->mappedWidth); /* mappedWidth (4 bytes) */
+ Stream_Write_UINT32(s, pdu->mappedHeight); /* mappedHeight (4 bytes) */
+ Stream_Write_UINT32(s, pdu->targetWidth); /* targetWidth (4 bytes) */
+ Stream_Write_UINT32(s, pdu->targetHeight); /* targetHeight (4 bytes) */
+ return rdpgfx_server_single_packet_send(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_frame_acknowledge_pdu(RdpgfxServerContext* context, wStream* s)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ RDPGFX_FRAME_ACKNOWLEDGE_PDU pdu;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, pdu.queueDepth); /* queueDepth (4 bytes) */
+ Stream_Read_UINT32(s, pdu.frameId); /* frameId (4 bytes) */
+ Stream_Read_UINT32(s, pdu.totalFramesDecoded); /* totalFramesDecoded (4 bytes) */
+
+ if (context)
+ {
+ IFCALLRET(context->FrameAcknowledge, error, context, &pdu);
+
+ if (error)
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "context->FrameAcknowledge failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_cache_import_offer_pdu(RdpgfxServerContext* context, wStream* s)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+
+ RDPGFX_CACHE_IMPORT_OFFER_PDU pdu = { 0 };
+ RDPGFX_CACHE_ENTRY_METADATA* cacheEntry = NULL;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return ERROR_INVALID_DATA;
+
+ /* cacheEntriesCount (2 bytes) */
+ Stream_Read_UINT16(s, pdu.cacheEntriesCount);
+
+ /* 2.2.2.16 RDPGFX_CACHE_IMPORT_OFFER_PDU */
+ if (pdu.cacheEntriesCount >= 5462)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Invalid cacheEntriesCount: %" PRIu16 "",
+ pdu.cacheEntriesCount);
+ return ERROR_INVALID_DATA;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, pdu.cacheEntriesCount, 12ull))
+ return ERROR_INVALID_DATA;
+
+ for (UINT16 index = 0; index < pdu.cacheEntriesCount; index++)
+ {
+ cacheEntry = &(pdu.cacheEntries[index]);
+ Stream_Read_UINT64(s, cacheEntry->cacheKey); /* cacheKey (8 bytes) */
+ Stream_Read_UINT32(s, cacheEntry->bitmapLength); /* bitmapLength (4 bytes) */
+ }
+
+ if (context)
+ {
+ IFCALLRET(context->CacheImportOffer, error, context, &pdu);
+
+ if (error)
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "context->CacheImportOffer failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_caps_advertise_pdu(RdpgfxServerContext* context, wStream* s)
+{
+ RDPGFX_CAPSET* capsSets = NULL;
+ RDPGFX_CAPS_ADVERTISE_PDU pdu = { 0 };
+ UINT error = ERROR_INVALID_DATA;
+
+ if (!context)
+ return ERROR_BAD_ARGUMENTS;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, pdu.capsSetCount); /* capsSetCount (2 bytes) */
+ if (pdu.capsSetCount > 0)
+ {
+ capsSets = calloc(pdu.capsSetCount, (RDPGFX_CAPSET_BASE_SIZE + 4));
+ if (!capsSets)
+ return ERROR_OUTOFMEMORY;
+ }
+
+ pdu.capsSets = capsSets;
+
+ for (UINT16 index = 0; index < pdu.capsSetCount; index++)
+ {
+ RDPGFX_CAPSET* capsSet = &(pdu.capsSets[index]);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ goto fail;
+
+ Stream_Read_UINT32(s, capsSet->version); /* version (4 bytes) */
+ Stream_Read_UINT32(s, capsSet->length); /* capsDataLength (4 bytes) */
+
+ if (capsSet->length >= 4)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ goto fail;
+
+ Stream_Peek_UINT32(s, capsSet->flags); /* capsData (4 bytes) */
+ }
+
+ if (!Stream_SafeSeek(s, capsSet->length))
+ goto fail;
+ }
+
+ error = ERROR_BAD_CONFIGURATION;
+ IFCALLRET(context->CapsAdvertise, error, context, &pdu);
+
+ if (error)
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "context->CapsAdvertise failed with error %" PRIu32 "", error);
+
+fail:
+ free(capsSets);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_recv_qoe_frame_acknowledge_pdu(RdpgfxServerContext* context, wStream* s)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ RDPGFX_QOE_FRAME_ACKNOWLEDGE_PDU pdu;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, pdu.frameId); /* frameId (4 bytes) */
+ Stream_Read_UINT32(s, pdu.timestamp); /* timestamp (4 bytes) */
+ Stream_Read_UINT16(s, pdu.timeDiffSE); /* timeDiffSE (2 bytes) */
+ Stream_Read_UINT16(s, pdu.timeDiffEDR); /* timeDiffEDR (2 bytes) */
+
+ if (context)
+ {
+ IFCALLRET(context->QoeFrameAcknowledge, error, context, &pdu);
+
+ if (error)
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "context->QoeFrameAcknowledge failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+static UINT
+rdpgfx_send_map_surface_to_scaled_output_pdu(RdpgfxServerContext* context,
+ const RDPGFX_MAP_SURFACE_TO_SCALED_OUTPUT_PDU* pdu)
+{
+ if (!checkCapsAreExchanged(context))
+ return CHANNEL_RC_NOT_INITIALIZED;
+ wStream* s = rdpgfx_server_single_packet_new(context->priv->log,
+ RDPGFX_CMDID_MAPSURFACETOSCALEDOUTPUT, 20);
+
+ if (!s)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* reserved (2 bytes). Must be 0 */
+ Stream_Write_UINT32(s, pdu->outputOriginX); /* outputOriginX (4 bytes) */
+ Stream_Write_UINT32(s, pdu->outputOriginY); /* outputOriginY (4 bytes) */
+ Stream_Write_UINT32(s, pdu->targetWidth); /* targetWidth (4 bytes) */
+ Stream_Write_UINT32(s, pdu->targetHeight); /* targetHeight (4 bytes) */
+ return rdpgfx_server_single_packet_send(context, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpgfx_server_receive_pdu(RdpgfxServerContext* context, wStream* s)
+{
+ size_t beg = 0;
+ size_t end = 0;
+ RDPGFX_HEADER header;
+ UINT error = CHANNEL_RC_OK;
+ beg = Stream_GetPosition(s);
+
+ if ((error = rdpgfx_read_header(s, &header)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpgfx_read_header failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+#ifdef WITH_DEBUG_RDPGFX
+ WLog_DBG(TAG, "cmdId: %s (0x%04" PRIX16 ") flags: 0x%04" PRIX16 " pduLength: %" PRIu32 "",
+ rdpgfx_get_cmd_id_string(header.cmdId), header.cmdId, header.flags, header.pduLength);
+#endif
+
+ switch (header.cmdId)
+ {
+ case RDPGFX_CMDID_FRAMEACKNOWLEDGE:
+ if ((error = rdpgfx_recv_frame_acknowledge_pdu(context, s)))
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpgfx_recv_frame_acknowledge_pdu "
+ "failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case RDPGFX_CMDID_CACHEIMPORTOFFER:
+ if ((error = rdpgfx_recv_cache_import_offer_pdu(context, s)))
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpgfx_recv_cache_import_offer_pdu "
+ "failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case RDPGFX_CMDID_CAPSADVERTISE:
+ if ((error = rdpgfx_recv_caps_advertise_pdu(context, s)))
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpgfx_recv_caps_advertise_pdu "
+ "failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case RDPGFX_CMDID_QOEFRAMEACKNOWLEDGE:
+ if ((error = rdpgfx_recv_qoe_frame_acknowledge_pdu(context, s)))
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpgfx_recv_qoe_frame_acknowledge_pdu "
+ "failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ default:
+ error = CHANNEL_RC_BAD_PROC;
+ break;
+ }
+
+ if (error)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "Error while parsing GFX cmdId: %s (0x%04" PRIX16 ")",
+ rdpgfx_get_cmd_id_string(header.cmdId), header.cmdId);
+ return error;
+ }
+
+ end = Stream_GetPosition(s);
+
+ if (end != (beg + header.pduLength))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "Unexpected gfx pdu end: Actual: %" PRIuz ", Expected: %" PRIuz "", end,
+ (beg + header.pduLength));
+ Stream_SetPosition(s, (beg + header.pduLength));
+ }
+
+ return error;
+}
+
+static BOOL rdpgfx_server_close(RdpgfxServerContext* context);
+
+static DWORD WINAPI rdpgfx_server_thread_func(LPVOID arg)
+{
+ RdpgfxServerContext* context = (RdpgfxServerContext*)arg;
+ WINPR_ASSERT(context);
+
+ RdpgfxServerPrivate* priv = context->priv;
+ DWORD status = 0;
+ DWORD nCount = 0;
+ HANDLE events[8] = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(priv);
+
+ if (priv->ownThread)
+ {
+ WINPR_ASSERT(priv->stopEvent);
+ events[nCount++] = priv->stopEvent;
+ }
+
+ WINPR_ASSERT(priv->channelEvent);
+ events[nCount++] = priv->channelEvent;
+
+ /* Main virtual channel loop. RDPGFX do not need version negotiation */
+ while (TRUE)
+ {
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "WaitForMultipleObjects failed with error %" PRIu32 "", error);
+ break;
+ }
+
+ /* Stop Event */
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ if ((error = rdpgfx_server_handle_messages(context)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpgfx_server_handle_messages failed with error %" PRIu32 "", error);
+ break;
+ }
+ }
+
+ if (error && context->rdpcontext)
+ setChannelError(context->rdpcontext, error, "rdpgfx_server_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+static BOOL rdpgfx_server_open(RdpgfxServerContext* context)
+{
+ WINPR_ASSERT(context);
+ RdpgfxServerPrivate* priv = (RdpgfxServerPrivate*)context->priv;
+ void* buffer = NULL;
+
+ WINPR_ASSERT(priv);
+
+ if (!priv->isOpened)
+ {
+ PULONG pSessionId = NULL;
+ DWORD BytesReturned = 0;
+ priv->SessionId = WTS_CURRENT_SESSION;
+ UINT32 channelId = 0;
+ BOOL status = TRUE;
+
+ if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId,
+ (LPSTR*)&pSessionId, &BytesReturned) == FALSE)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "WTSQuerySessionInformationA failed!");
+ return FALSE;
+ }
+
+ priv->SessionId = (DWORD)*pSessionId;
+ WTSFreeMemory(pSessionId);
+ priv->rdpgfx_channel = WTSVirtualChannelOpenEx(priv->SessionId, RDPGFX_DVC_CHANNEL_NAME,
+ WTS_CHANNEL_OPTION_DYNAMIC);
+
+ if (!priv->rdpgfx_channel)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelOpenEx failed!");
+ return FALSE;
+ }
+
+ channelId = WTSChannelGetIdByHandle(priv->rdpgfx_channel);
+
+ IFCALLRET(context->ChannelIdAssigned, status, context, channelId);
+ if (!status)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "context->ChannelIdAssigned failed!");
+ goto fail;
+ }
+
+ /* Query for channel event handle */
+ if (!WTSVirtualChannelQuery(priv->rdpgfx_channel, WTSVirtualEventHandle, &buffer,
+ &BytesReturned) ||
+ (BytesReturned != sizeof(HANDLE)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "WTSVirtualChannelQuery failed "
+ "or invalid returned size(%" PRIu32 ")",
+ BytesReturned);
+
+ if (buffer)
+ WTSFreeMemory(buffer);
+
+ goto fail;
+ }
+
+ CopyMemory(&priv->channelEvent, buffer, sizeof(HANDLE));
+ WTSFreeMemory(buffer);
+
+ if (!(priv->zgfx = zgfx_context_new(TRUE)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Create zgfx context failed!");
+ goto fail;
+ }
+
+ priv->isReady = FALSE;
+ const RDPGFX_CAPSET empty = { 0 };
+ priv->activeCapSet = empty;
+ if (priv->ownThread)
+ {
+ if (!(priv->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "CreateEvent failed!");
+ goto fail;
+ }
+
+ if (!(priv->thread =
+ CreateThread(NULL, 0, rdpgfx_server_thread_func, (void*)context, 0, NULL)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "CreateThread failed!");
+ goto fail;
+ }
+ }
+
+ priv->isOpened = TRUE;
+ return TRUE;
+ }
+
+ WLog_Print(context->priv->log, WLOG_ERROR, "RDPGFX channel is already opened!");
+ return FALSE;
+fail:
+ rdpgfx_server_close(context);
+ return FALSE;
+}
+
+BOOL rdpgfx_server_close(RdpgfxServerContext* context)
+{
+ WINPR_ASSERT(context);
+
+ RdpgfxServerPrivate* priv = (RdpgfxServerPrivate*)context->priv;
+ WINPR_ASSERT(priv);
+
+ if (priv->ownThread && priv->thread)
+ {
+ SetEvent(priv->stopEvent);
+
+ if (WaitForSingleObject(priv->thread, INFINITE) == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", GetLastError());
+ return FALSE;
+ }
+
+ CloseHandle(priv->thread);
+ CloseHandle(priv->stopEvent);
+ priv->thread = NULL;
+ priv->stopEvent = NULL;
+ }
+
+ zgfx_context_free(priv->zgfx);
+ priv->zgfx = NULL;
+
+ if (priv->rdpgfx_channel)
+ {
+ WTSVirtualChannelClose(priv->rdpgfx_channel);
+ priv->rdpgfx_channel = NULL;
+ }
+
+ priv->channelEvent = NULL;
+ priv->isOpened = FALSE;
+ priv->isReady = FALSE;
+ const RDPGFX_CAPSET empty = { 0 };
+ priv->activeCapSet = empty;
+ return TRUE;
+}
+
+static BOOL rdpgfx_server_initialize(RdpgfxServerContext* context, BOOL externalThread)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if (context->priv->isOpened)
+ {
+ WLog_Print(context->priv->log, WLOG_WARN,
+ "Application error: RDPEGFX channel already initialized, "
+ "calling in this state is not possible!");
+ return FALSE;
+ }
+
+ context->priv->ownThread = !externalThread;
+ return TRUE;
+}
+
+RdpgfxServerContext* rdpgfx_server_context_new(HANDLE vcm)
+{
+ RdpgfxServerContext* context = (RdpgfxServerContext*)calloc(1, sizeof(RdpgfxServerContext));
+
+ if (!context)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return NULL;
+ }
+
+ context->vcm = vcm;
+ context->Initialize = rdpgfx_server_initialize;
+ context->Open = rdpgfx_server_open;
+ context->Close = rdpgfx_server_close;
+ context->ResetGraphics = rdpgfx_send_reset_graphics_pdu;
+ context->StartFrame = rdpgfx_send_start_frame_pdu;
+ context->EndFrame = rdpgfx_send_end_frame_pdu;
+ context->SurfaceCommand = rdpgfx_send_surface_command;
+ context->SurfaceFrameCommand = rdpgfx_send_surface_frame_command;
+ context->DeleteEncodingContext = rdpgfx_send_delete_encoding_context_pdu;
+ context->CreateSurface = rdpgfx_send_create_surface_pdu;
+ context->DeleteSurface = rdpgfx_send_delete_surface_pdu;
+ context->SolidFill = rdpgfx_send_solid_fill_pdu;
+ context->SurfaceToSurface = rdpgfx_send_surface_to_surface_pdu;
+ context->SurfaceToCache = rdpgfx_send_surface_to_cache_pdu;
+ context->CacheToSurface = rdpgfx_send_cache_to_surface_pdu;
+ context->CacheImportOffer = rdpgfx_process_cache_import_offer_pdu;
+ context->CacheImportReply = rdpgfx_send_cache_import_reply_pdu;
+ context->EvictCacheEntry = rdpgfx_send_evict_cache_entry_pdu;
+ context->MapSurfaceToOutput = rdpgfx_send_map_surface_to_output_pdu;
+ context->MapSurfaceToWindow = rdpgfx_send_map_surface_to_window_pdu;
+ context->MapSurfaceToScaledOutput = rdpgfx_send_map_surface_to_scaled_output_pdu;
+ context->MapSurfaceToScaledWindow = rdpgfx_send_map_surface_to_scaled_window_pdu;
+ context->CapsAdvertise = NULL;
+ context->CapsConfirm = rdpgfx_send_caps_confirm_pdu;
+ context->FrameAcknowledge = NULL;
+ context->QoeFrameAcknowledge = NULL;
+ RdpgfxServerPrivate* priv = context->priv =
+ (RdpgfxServerPrivate*)calloc(1, sizeof(RdpgfxServerPrivate));
+
+ if (!priv)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ goto fail;
+ }
+
+ priv->log = WLog_Get(TAG);
+ if (!priv->log)
+ goto fail;
+
+ /* Create shared input stream */
+ priv->input_stream = Stream_New(NULL, 4);
+
+ if (!priv->input_stream)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!");
+ goto fail;
+ }
+
+ priv->isOpened = FALSE;
+ priv->isReady = FALSE;
+ priv->ownThread = TRUE;
+
+ const RDPGFX_CAPSET empty = { 0 };
+ priv->activeCapSet = empty;
+ return context;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ rdpgfx_server_context_free(context);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void rdpgfx_server_context_free(RdpgfxServerContext* context)
+{
+ if (!context)
+ return;
+
+ rdpgfx_server_close(context);
+
+ if (context->priv)
+ Stream_Free(context->priv->input_stream, TRUE);
+
+ free(context->priv);
+ free(context);
+}
+
+HANDLE rdpgfx_server_get_event_handle(RdpgfxServerContext* context)
+{
+ if (!context)
+ return NULL;
+ if (!context->priv)
+ return NULL;
+ return context->priv->channelEvent;
+}
+
+/*
+ * Handle rpdgfx messages - server side
+ *
+ * @param Server side context
+ *
+ * @return 0 on success
+ * ERROR_NO_DATA if no data could be read this time
+ * otherwise a Win32 error code
+ */
+UINT rdpgfx_server_handle_messages(RdpgfxServerContext* context)
+{
+ DWORD BytesReturned = 0;
+ void* buffer = NULL;
+ UINT ret = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ RdpgfxServerPrivate* priv = context->priv;
+ wStream* s = priv->input_stream;
+
+ /* Check whether the dynamic channel is ready */
+ if (!priv->isReady)
+ {
+ if (WTSVirtualChannelQuery(priv->rdpgfx_channel, WTSVirtualChannelReady, &buffer,
+ &BytesReturned) == FALSE)
+ {
+ if (GetLastError() == ERROR_NO_DATA)
+ return ERROR_NO_DATA;
+
+ WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelQuery failed");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ priv->isReady = *((BOOL*)buffer);
+ WTSFreeMemory(buffer);
+ }
+
+ /* Consume channel event only after the gfx dynamic channel is ready */
+ if (priv->isReady)
+ {
+ Stream_SetPosition(s, 0);
+
+ if (!WTSVirtualChannelRead(priv->rdpgfx_channel, 0, NULL, 0, &BytesReturned))
+ {
+ if (GetLastError() == ERROR_NO_DATA)
+ return ERROR_NO_DATA;
+
+ WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelRead failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (BytesReturned < 1)
+ return CHANNEL_RC_OK;
+
+ if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if (WTSVirtualChannelRead(priv->rdpgfx_channel, 0, (PCHAR)Stream_Buffer(s),
+ Stream_Capacity(s), &BytesReturned) == FALSE)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelRead failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_SetLength(s, BytesReturned);
+ Stream_SetPosition(s, 0);
+
+ while (Stream_GetPosition(s) < Stream_Length(s))
+ {
+ if ((ret = rdpgfx_server_receive_pdu(context, s)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "rdpgfx_server_receive_pdu "
+ "failed with error %" PRIu32 "!",
+ ret);
+ return ret;
+ }
+ }
+ }
+
+ return ret;
+}
diff --git a/channels/rdpgfx/server/rdpgfx_main.h b/channels/rdpgfx/server/rdpgfx_main.h
new file mode 100644
index 0000000..8b184bb
--- /dev/null
+++ b/channels/rdpgfx/server/rdpgfx_main.h
@@ -0,0 +1,42 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Graphics Pipeline Extension
+ *
+ * Copyright 2016 Jiang Zihao <zihao.jiang@yahoo.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPGFX_SERVER_MAIN_H
+#define FREERDP_CHANNEL_RDPGFX_SERVER_MAIN_H
+
+#include <freerdp/server/rdpgfx.h>
+#include <freerdp/codec/zgfx.h>
+
+struct s_rdpgfx_server_private
+{
+ ZGFX_CONTEXT* zgfx;
+ BOOL ownThread;
+ HANDLE thread;
+ HANDLE stopEvent;
+ HANDLE channelEvent;
+ void* rdpgfx_channel;
+ DWORD SessionId;
+ wStream* input_stream;
+ BOOL isOpened;
+ BOOL isReady;
+ wLog* log;
+ RDPGFX_CAPSET activeCapSet;
+};
+
+#endif /* FREERDP_CHANNEL_RDPGFX_SERVER_MAIN_H */
diff --git a/channels/rdpsnd/CMakeLists.txt b/channels/rdpsnd/CMakeLists.txt
new file mode 100644
index 0000000..08b6836
--- /dev/null
+++ b/channels/rdpsnd/CMakeLists.txt
@@ -0,0 +1,29 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("rdpsnd")
+
+include_directories(common)
+add_subdirectory(common)
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/rdpsnd/ChannelOptions.cmake b/channels/rdpsnd/ChannelOptions.cmake
new file mode 100644
index 0000000..948ba97
--- /dev/null
+++ b/channels/rdpsnd/ChannelOptions.cmake
@@ -0,0 +1,13 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT ON)
+
+define_channel_options(NAME "rdpsnd" TYPE "static;dynamic"
+ DESCRIPTION "Audio Output Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPEA]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/rdpsnd/client/CMakeLists.txt b/channels/rdpsnd/client/CMakeLists.txt
new file mode 100644
index 0000000..fc8cae2
--- /dev/null
+++ b/channels/rdpsnd/client/CMakeLists.txt
@@ -0,0 +1,66 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("rdpsnd")
+
+set(${MODULE_PREFIX}_SRCS
+ rdpsnd_main.c
+ rdpsnd_main.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${CMAKE_THREAD_LIBS_INIT}
+ rdpsnd-common
+)
+
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx;DVCPluginEntry")
+
+if(WITH_OSS)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "oss" "")
+endif()
+
+if(WITH_ALSA)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "alsa" "")
+endif()
+
+if(WITH_IOSAUDIO)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "ios" "")
+endif()
+
+if(WITH_PULSE)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "pulse" "")
+endif()
+
+if(WITH_MACAUDIO)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "mac" "")
+endif()
+
+if(WITH_WINMM)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "winmm" "")
+endif()
+
+if(WITH_OPENSLES)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "opensles" "")
+endif()
+
+if(WITH_SNDIO)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "sndio" "")
+endif()
+
+add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "fake" "")
diff --git a/channels/rdpsnd/client/alsa/CMakeLists.txt b/channels/rdpsnd/client/alsa/CMakeLists.txt
new file mode 100644
index 0000000..3041b95
--- /dev/null
+++ b/channels/rdpsnd/client/alsa/CMakeLists.txt
@@ -0,0 +1,34 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("rdpsnd" "alsa" "")
+
+find_package(ALSA REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ rdpsnd_alsa.c)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${ALSA_LIBRARIES}
+)
+
+include_directories(..)
+include_directories(${ALSA_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/rdpsnd/client/alsa/rdpsnd_alsa.c b/channels/rdpsnd/client/alsa/rdpsnd_alsa.c
new file mode 100644
index 0000000..97f0ba0
--- /dev/null
+++ b/channels/rdpsnd/client/alsa/rdpsnd_alsa.c
@@ -0,0 +1,574 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Output Virtual Channel
+ *
+ * Copyright 2009-2011 Jay Sorg
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/cmdline.h>
+#include <winpr/sysinfo.h>
+#include <winpr/collections.h>
+
+#include <alsa/asoundlib.h>
+
+#include <freerdp/types.h>
+#include <freerdp/codec/dsp.h>
+#include <freerdp/channels/log.h>
+
+#include "rdpsnd_main.h"
+
+typedef struct
+{
+ rdpsndDevicePlugin device;
+
+ UINT32 latency;
+ AUDIO_FORMAT aformat;
+ char* device_name;
+ snd_pcm_t* pcm_handle;
+ snd_mixer_t* mixer_handle;
+
+ UINT32 actual_rate;
+ snd_pcm_format_t format;
+ UINT32 actual_channels;
+
+ snd_pcm_uframes_t buffer_size;
+ snd_pcm_uframes_t period_size;
+} rdpsndAlsaPlugin;
+
+#define SND_PCM_CHECK(_func, _status) \
+ do \
+ { \
+ if (_status < 0) \
+ { \
+ WLog_ERR(TAG, "%s: %d\n", _func, _status); \
+ return -1; \
+ } \
+ } while (0)
+
+static int rdpsnd_alsa_set_hw_params(rdpsndAlsaPlugin* alsa)
+{
+ int status = 0;
+ snd_pcm_hw_params_t* hw_params = NULL;
+ snd_pcm_uframes_t buffer_size_max = 0;
+ status = snd_pcm_hw_params_malloc(&hw_params);
+ SND_PCM_CHECK("snd_pcm_hw_params_malloc", status);
+ status = snd_pcm_hw_params_any(alsa->pcm_handle, hw_params);
+ SND_PCM_CHECK("snd_pcm_hw_params_any", status);
+ /* Set interleaved read/write access */
+ status =
+ snd_pcm_hw_params_set_access(alsa->pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
+ SND_PCM_CHECK("snd_pcm_hw_params_set_access", status);
+ /* Set sample format */
+ status = snd_pcm_hw_params_set_format(alsa->pcm_handle, hw_params, alsa->format);
+ SND_PCM_CHECK("snd_pcm_hw_params_set_format", status);
+ /* Set sample rate */
+ status = snd_pcm_hw_params_set_rate_near(alsa->pcm_handle, hw_params, &alsa->actual_rate, NULL);
+ SND_PCM_CHECK("snd_pcm_hw_params_set_rate_near", status);
+ /* Set number of channels */
+ status = snd_pcm_hw_params_set_channels(alsa->pcm_handle, hw_params, alsa->actual_channels);
+ SND_PCM_CHECK("snd_pcm_hw_params_set_channels", status);
+ /* Get maximum buffer size */
+ status = snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size_max);
+ SND_PCM_CHECK("snd_pcm_hw_params_get_buffer_size_max", status);
+ /**
+ * ALSA Parameters
+ *
+ * http://www.alsa-project.org/main/index.php/FramesPeriods
+ *
+ * buffer_size = period_size * periods
+ * period_bytes = period_size * bytes_per_frame
+ * bytes_per_frame = channels * bytes_per_sample
+ *
+ * A frame is equivalent of one sample being played,
+ * irrespective of the number of channels or the number of bits
+ *
+ * A period is the number of frames in between each hardware interrupt.
+ *
+ * The buffer size always has to be greater than one period size.
+ * Commonly this is (2 * period_size), but some hardware can do 8 periods per buffer.
+ * It is also possible for the buffer size to not be an integer multiple of the period size.
+ */
+ int interrupts_per_sec_near = 50;
+ int bytes_per_sec =
+ (alsa->actual_rate * alsa->aformat.wBitsPerSample / 8 * alsa->actual_channels);
+ alsa->buffer_size = buffer_size_max;
+ alsa->period_size = (bytes_per_sec / interrupts_per_sec_near);
+
+ if (alsa->period_size > buffer_size_max)
+ {
+ WLog_ERR(TAG, "Warning: requested sound buffer size %lu, got %lu instead\n",
+ alsa->buffer_size, buffer_size_max);
+ alsa->period_size = (buffer_size_max / 8);
+ }
+
+ /* Set buffer size */
+ status =
+ snd_pcm_hw_params_set_buffer_size_near(alsa->pcm_handle, hw_params, &alsa->buffer_size);
+ SND_PCM_CHECK("snd_pcm_hw_params_set_buffer_size_near", status);
+ /* Set period size */
+ status = snd_pcm_hw_params_set_period_size_near(alsa->pcm_handle, hw_params, &alsa->period_size,
+ NULL);
+ SND_PCM_CHECK("snd_pcm_hw_params_set_period_size_near", status);
+ status = snd_pcm_hw_params(alsa->pcm_handle, hw_params);
+ SND_PCM_CHECK("snd_pcm_hw_params", status);
+ snd_pcm_hw_params_free(hw_params);
+ return 0;
+}
+
+static int rdpsnd_alsa_set_sw_params(rdpsndAlsaPlugin* alsa)
+{
+ int status = 0;
+ snd_pcm_sw_params_t* sw_params = NULL;
+ status = snd_pcm_sw_params_malloc(&sw_params);
+ SND_PCM_CHECK("snd_pcm_sw_params_malloc", status);
+ status = snd_pcm_sw_params_current(alsa->pcm_handle, sw_params);
+ SND_PCM_CHECK("snd_pcm_sw_params_current", status);
+ status = snd_pcm_sw_params_set_avail_min(alsa->pcm_handle, sw_params,
+ (alsa->aformat.nChannels * alsa->actual_channels));
+ SND_PCM_CHECK("snd_pcm_sw_params_set_avail_min", status);
+ status = snd_pcm_sw_params_set_start_threshold(alsa->pcm_handle, sw_params,
+ alsa->aformat.nBlockAlign);
+ SND_PCM_CHECK("snd_pcm_sw_params_set_start_threshold", status);
+ status = snd_pcm_sw_params(alsa->pcm_handle, sw_params);
+ SND_PCM_CHECK("snd_pcm_sw_params", status);
+ snd_pcm_sw_params_free(sw_params);
+ status = snd_pcm_prepare(alsa->pcm_handle);
+ SND_PCM_CHECK("snd_pcm_prepare", status);
+ return 0;
+}
+
+static int rdpsnd_alsa_validate_params(rdpsndAlsaPlugin* alsa)
+{
+ int status = 0;
+ snd_pcm_uframes_t buffer_size = 0;
+ snd_pcm_uframes_t period_size = 0;
+ status = snd_pcm_get_params(alsa->pcm_handle, &buffer_size, &period_size);
+ SND_PCM_CHECK("snd_pcm_get_params", status);
+ return 0;
+}
+
+static int rdpsnd_alsa_set_params(rdpsndAlsaPlugin* alsa)
+{
+ snd_pcm_drop(alsa->pcm_handle);
+
+ if (rdpsnd_alsa_set_hw_params(alsa) < 0)
+ return -1;
+
+ if (rdpsnd_alsa_set_sw_params(alsa) < 0)
+ return -1;
+
+ return rdpsnd_alsa_validate_params(alsa);
+}
+
+static BOOL rdpsnd_alsa_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
+ UINT32 latency)
+{
+ rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
+
+ if (format)
+ {
+ alsa->aformat = *format;
+ alsa->actual_rate = format->nSamplesPerSec;
+ alsa->actual_channels = format->nChannels;
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ switch (format->wBitsPerSample)
+ {
+ case 8:
+ alsa->format = SND_PCM_FORMAT_S8;
+ break;
+
+ case 16:
+ alsa->format = SND_PCM_FORMAT_S16_LE;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ break;
+
+ default:
+ return FALSE;
+ }
+ }
+
+ alsa->latency = latency;
+ return (rdpsnd_alsa_set_params(alsa) == 0);
+}
+
+static void rdpsnd_alsa_close_mixer(rdpsndAlsaPlugin* alsa)
+{
+ if (alsa && alsa->mixer_handle)
+ {
+ snd_mixer_close(alsa->mixer_handle);
+ alsa->mixer_handle = NULL;
+ }
+}
+
+static BOOL rdpsnd_alsa_open_mixer(rdpsndAlsaPlugin* alsa)
+{
+ int status = 0;
+
+ if (alsa->mixer_handle)
+ return TRUE;
+
+ status = snd_mixer_open(&alsa->mixer_handle, 0);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "snd_mixer_open failed");
+ goto fail;
+ }
+
+ status = snd_mixer_attach(alsa->mixer_handle, alsa->device_name);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "snd_mixer_attach failed");
+ goto fail;
+ }
+
+ status = snd_mixer_selem_register(alsa->mixer_handle, NULL, NULL);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "snd_mixer_selem_register failed");
+ goto fail;
+ }
+
+ status = snd_mixer_load(alsa->mixer_handle);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "snd_mixer_load failed");
+ goto fail;
+ }
+
+ return TRUE;
+fail:
+ rdpsnd_alsa_close_mixer(alsa);
+ return FALSE;
+}
+
+static void rdpsnd_alsa_pcm_close(rdpsndAlsaPlugin* alsa)
+{
+ if (alsa && alsa->pcm_handle)
+ {
+ snd_pcm_drain(alsa->pcm_handle);
+ snd_pcm_close(alsa->pcm_handle);
+ alsa->pcm_handle = 0;
+ }
+}
+
+static BOOL rdpsnd_alsa_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency)
+{
+ int mode = 0;
+ int status = 0;
+ rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
+
+ if (alsa->pcm_handle)
+ return TRUE;
+
+ mode = 0;
+ /*mode |= SND_PCM_NONBLOCK;*/
+ status = snd_pcm_open(&alsa->pcm_handle, alsa->device_name, SND_PCM_STREAM_PLAYBACK, mode);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "snd_pcm_open failed");
+ return FALSE;
+ }
+
+ return rdpsnd_alsa_set_format(device, format, latency) && rdpsnd_alsa_open_mixer(alsa);
+}
+
+static void rdpsnd_alsa_close(rdpsndDevicePlugin* device)
+{
+ rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
+
+ if (!alsa)
+ return;
+
+ rdpsnd_alsa_close_mixer(alsa);
+}
+
+static void rdpsnd_alsa_free(rdpsndDevicePlugin* device)
+{
+ rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
+ rdpsnd_alsa_pcm_close(alsa);
+ rdpsnd_alsa_close_mixer(alsa);
+ free(alsa->device_name);
+ free(alsa);
+}
+
+static BOOL rdpsnd_alsa_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
+{
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ if (format->cbSize == 0 && format->nSamplesPerSec <= 48000 &&
+ (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
+ (format->nChannels == 1 || format->nChannels == 2))
+ {
+ return TRUE;
+ }
+
+ break;
+ }
+
+ return FALSE;
+}
+
+static UINT32 rdpsnd_alsa_get_volume(rdpsndDevicePlugin* device)
+{
+ long volume_min = 0;
+ long volume_max = 0;
+ long volume_left = 0;
+ long volume_right = 0;
+ UINT32 dwVolume = 0;
+ UINT16 dwVolumeLeft = 0;
+ UINT16 dwVolumeRight = 0;
+ rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
+ dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */
+ dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */
+
+ if (!rdpsnd_alsa_open_mixer(alsa))
+ return 0;
+
+ for (snd_mixer_elem_t* elem = snd_mixer_first_elem(alsa->mixer_handle); elem;
+ elem = snd_mixer_elem_next(elem))
+ {
+ if (snd_mixer_selem_has_playback_volume(elem))
+ {
+ snd_mixer_selem_get_playback_volume_range(elem, &volume_min, &volume_max);
+ snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &volume_left);
+ snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, &volume_right);
+ dwVolumeLeft =
+ (UINT16)(((volume_left * 0xFFFF) - volume_min) / (volume_max - volume_min));
+ dwVolumeRight =
+ (UINT16)(((volume_right * 0xFFFF) - volume_min) / (volume_max - volume_min));
+ break;
+ }
+ }
+
+ dwVolume = (dwVolumeLeft << 16) | dwVolumeRight;
+ return dwVolume;
+}
+
+static BOOL rdpsnd_alsa_set_volume(rdpsndDevicePlugin* device, UINT32 value)
+{
+ long left = 0;
+ long right = 0;
+ long volume_min = 0;
+ long volume_max = 0;
+ long volume_left = 0;
+ long volume_right = 0;
+ rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
+
+ if (!rdpsnd_alsa_open_mixer(alsa))
+ return FALSE;
+
+ left = (value & 0xFFFF);
+ right = ((value >> 16) & 0xFFFF);
+
+ for (snd_mixer_elem_t* elem = snd_mixer_first_elem(alsa->mixer_handle); elem;
+ elem = snd_mixer_elem_next(elem))
+ {
+ if (snd_mixer_selem_has_playback_volume(elem))
+ {
+ snd_mixer_selem_get_playback_volume_range(elem, &volume_min, &volume_max);
+ volume_left = volume_min + (left * (volume_max - volume_min)) / 0xFFFF;
+ volume_right = volume_min + (right * (volume_max - volume_min)) / 0xFFFF;
+
+ if ((snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, volume_left) <
+ 0) ||
+ (snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT,
+ volume_right) < 0))
+ {
+ WLog_ERR(TAG, "error setting the volume\n");
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static UINT rdpsnd_alsa_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
+{
+ UINT latency = 0;
+ size_t offset = 0;
+ int frame_size = 0;
+ rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
+ WINPR_ASSERT(alsa);
+ WINPR_ASSERT(data || (size == 0));
+ frame_size = alsa->actual_channels * alsa->aformat.wBitsPerSample / 8;
+ if (frame_size <= 0)
+ return 0;
+
+ while (offset < size)
+ {
+ snd_pcm_sframes_t status =
+ snd_pcm_writei(alsa->pcm_handle, &data[offset], (size - offset) / frame_size);
+
+ if (status < 0)
+ status = snd_pcm_recover(alsa->pcm_handle, status, 0);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "status: %d\n", status);
+ rdpsnd_alsa_close(device);
+ rdpsnd_alsa_open(device, NULL, alsa->latency);
+ break;
+ }
+
+ offset += status * frame_size;
+ }
+
+ {
+ snd_pcm_sframes_t available = 0;
+ snd_pcm_sframes_t delay = 0;
+ int rc = snd_pcm_avail_delay(alsa->pcm_handle, &available, &delay);
+
+ if (rc != 0)
+ latency = 0;
+ else if (available == 0) /* Get [ms] from number of samples */
+ latency = delay * 1000 / alsa->actual_rate;
+ else
+ latency = 0;
+ }
+
+ return latency + alsa->latency;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_alsa_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
+{
+ int status = 0;
+ DWORD flags = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
+ COMMAND_LINE_ARGUMENT_A rdpsnd_alsa_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
+ NULL, NULL, -1, NULL, "device" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_alsa_args, flags, alsa, NULL,
+ NULL);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "CommandLineParseArgumentsA failed!");
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+ }
+
+ arg = rdpsnd_alsa_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
+ {
+ alsa->device_name = _strdup(arg->Value);
+
+ if (!alsa->device_name)
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT alsa_freerdp_rdpsnd_client_subsystem_entry(
+ PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ const ADDIN_ARGV* args = NULL;
+ rdpsndAlsaPlugin* alsa = NULL;
+ UINT error = 0;
+ alsa = (rdpsndAlsaPlugin*)calloc(1, sizeof(rdpsndAlsaPlugin));
+
+ if (!alsa)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ alsa->device.Open = rdpsnd_alsa_open;
+ alsa->device.FormatSupported = rdpsnd_alsa_format_supported;
+ alsa->device.GetVolume = rdpsnd_alsa_get_volume;
+ alsa->device.SetVolume = rdpsnd_alsa_set_volume;
+ alsa->device.Play = rdpsnd_alsa_play;
+ alsa->device.Close = rdpsnd_alsa_close;
+ alsa->device.Free = rdpsnd_alsa_free;
+ args = pEntryPoints->args;
+
+ if (args->argc > 1)
+ {
+ if ((error = rdpsnd_alsa_parse_addin_args(&alsa->device, args)))
+ {
+ WLog_ERR(TAG, "rdpsnd_alsa_parse_addin_args failed with error %" PRIu32 "", error);
+ goto error_parse_args;
+ }
+ }
+
+ if (!alsa->device_name)
+ {
+ alsa->device_name = _strdup("default");
+
+ if (!alsa->device_name)
+ {
+ WLog_ERR(TAG, "_strdup failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_strdup;
+ }
+ }
+
+ alsa->pcm_handle = 0;
+ alsa->actual_rate = 22050;
+ alsa->format = SND_PCM_FORMAT_S16_LE;
+ alsa->actual_channels = 2;
+ pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)alsa);
+ return CHANNEL_RC_OK;
+error_strdup:
+ free(alsa->device_name);
+error_parse_args:
+ free(alsa);
+ return error;
+}
diff --git a/channels/rdpsnd/client/fake/CMakeLists.txt b/channels/rdpsnd/client/fake/CMakeLists.txt
new file mode 100644
index 0000000..a41b41f
--- /dev/null
+++ b/channels/rdpsnd/client/fake/CMakeLists.txt
@@ -0,0 +1,32 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2019 Armin Novak <armin.novak@thincast.com>
+# Copyright 2019 Thincast Technologies GmbH
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("rdpsnd" "fake" "")
+
+set(${MODULE_PREFIX}_SRCS
+ rdpsnd_fake.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+)
+
+include_directories(..)
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/rdpsnd/client/fake/rdpsnd_fake.c b/channels/rdpsnd/client/fake/rdpsnd_fake.c
new file mode 100644
index 0000000..35251d2
--- /dev/null
+++ b/channels/rdpsnd/client/fake/rdpsnd_fake.c
@@ -0,0 +1,147 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Output Virtual Channel
+ *
+ * Copyright 2019 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2019 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+#include <winpr/cmdline.h>
+
+#include <freerdp/types.h>
+#include <freerdp/settings.h>
+
+#include "rdpsnd_main.h"
+
+typedef struct
+{
+ rdpsndDevicePlugin device;
+} rdpsndFakePlugin;
+
+static BOOL rdpsnd_fake_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency)
+{
+ return TRUE;
+}
+
+static void rdpsnd_fake_close(rdpsndDevicePlugin* device)
+{
+}
+
+static BOOL rdpsnd_fake_set_volume(rdpsndDevicePlugin* device, UINT32 value)
+{
+ return TRUE;
+}
+
+static void rdpsnd_fake_free(rdpsndDevicePlugin* device)
+{
+ rdpsndFakePlugin* fake = (rdpsndFakePlugin*)device;
+
+ if (!fake)
+ return;
+
+ free(fake);
+}
+
+static BOOL rdpsnd_fake_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
+{
+ return TRUE;
+}
+
+static UINT rdpsnd_fake_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
+{
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_fake_parse_addin_args(rdpsndFakePlugin* fake, const ADDIN_ARGV* args)
+{
+ int status = 0;
+ DWORD flags = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ COMMAND_LINE_ARGUMENT_A rdpsnd_fake_args[] = { { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_fake_args, flags, fake, NULL,
+ NULL);
+
+ if (status < 0)
+ return ERROR_INVALID_DATA;
+
+ arg = rdpsnd_fake_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT fake_freerdp_rdpsnd_client_subsystem_entry(
+ PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ const ADDIN_ARGV* args = NULL;
+ rdpsndFakePlugin* fake = NULL;
+ UINT ret = CHANNEL_RC_OK;
+ fake = (rdpsndFakePlugin*)calloc(1, sizeof(rdpsndFakePlugin));
+
+ if (!fake)
+ return CHANNEL_RC_NO_MEMORY;
+
+ fake->device.Open = rdpsnd_fake_open;
+ fake->device.FormatSupported = rdpsnd_fake_format_supported;
+ fake->device.SetVolume = rdpsnd_fake_set_volume;
+ fake->device.Play = rdpsnd_fake_play;
+ fake->device.Close = rdpsnd_fake_close;
+ fake->device.Free = rdpsnd_fake_free;
+ args = pEntryPoints->args;
+
+ if (args->argc > 1)
+ {
+ ret = rdpsnd_fake_parse_addin_args(fake, args);
+
+ if (ret != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "error parsing arguments");
+ goto error;
+ }
+ }
+
+ pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, &fake->device);
+ return ret;
+error:
+ rdpsnd_fake_free(&fake->device);
+ return ret;
+}
diff --git a/channels/rdpsnd/client/ios/CMakeLists.txt b/channels/rdpsnd/client/ios/CMakeLists.txt
new file mode 100644
index 0000000..bfb3903
--- /dev/null
+++ b/channels/rdpsnd/client/ios/CMakeLists.txt
@@ -0,0 +1,40 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Laxmikant Rashinkar <LK.Rashinkar@gmail.com>
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+# Copyright 2013 Corey Clayton <can.of.tuna@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("rdpsnd" "ios" "")
+
+FIND_LIBRARY(CORE_AUDIO CoreAudio)
+FIND_LIBRARY(AUDIO_TOOL AudioToolbox)
+FIND_LIBRARY(CORE_FOUNDATION CoreFoundation)
+
+set(${MODULE_PREFIX}_SRCS
+ rdpsnd_ios.c
+ TPCircularBuffer.c)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${AUDIO_TOOL}
+ ${CORE_AUDIO}
+ ${CORE_FOUNDATION}
+)
+
+include_directories(..)
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/rdpsnd/client/ios/TPCircularBuffer.c b/channels/rdpsnd/client/ios/TPCircularBuffer.c
new file mode 100644
index 0000000..b29f611
--- /dev/null
+++ b/channels/rdpsnd/client/ios/TPCircularBuffer.c
@@ -0,0 +1,153 @@
+//
+// TPCircularBuffer.c
+// Circular/Ring buffer implementation
+//
+// https://github.com/michaeltyson/TPCircularBuffer
+//
+// Created by Michael Tyson on 10/12/2011.
+//
+// Copyright (C) 2012-2013 A Tasty Pixel
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+
+#include <winpr/wlog.h>
+
+#include "TPCircularBuffer.h"
+#include "rdpsnd_main.h"
+
+#include <mach/mach.h>
+#include <stdio.h>
+
+#define reportResult(result, operation) (_reportResult((result), (operation), __FILE__, __LINE__))
+static inline bool _reportResult(kern_return_t result, const char* operation, const char* file,
+ int line)
+{
+ if (result != ERR_SUCCESS)
+ {
+ WLog_DBG(TAG, "%s:%d: %s: %s\n", file, line, operation, mach_error_string(result));
+ return false;
+ }
+ return true;
+}
+
+bool TPCircularBufferInit(TPCircularBuffer* buffer, int length)
+{
+
+ // Keep trying until we get our buffer, needed to handle race conditions
+ int retries = 3;
+ while (true)
+ {
+
+ buffer->length = round_page(length); // We need whole page sizes
+
+ // Temporarily allocate twice the length, so we have the contiguous address space to
+ // support a second instance of the buffer directly after
+ vm_address_t bufferAddress;
+ kern_return_t result = vm_allocate(mach_task_self(), &bufferAddress, buffer->length * 2,
+ VM_FLAGS_ANYWHERE); // allocate anywhere it'll fit
+ if (result != ERR_SUCCESS)
+ {
+ if (retries-- == 0)
+ {
+ reportResult(result, "Buffer allocation");
+ return false;
+ }
+ // Try again if we fail
+ continue;
+ }
+
+ // Now replace the second half of the allocation with a virtual copy of the first half.
+ // Deallocate the second half...
+ result = vm_deallocate(mach_task_self(), bufferAddress + buffer->length, buffer->length);
+ if (result != ERR_SUCCESS)
+ {
+ if (retries-- == 0)
+ {
+ reportResult(result, "Buffer deallocation");
+ return false;
+ }
+ // If this fails somehow, deallocate the whole region and try again
+ vm_deallocate(mach_task_self(), bufferAddress, buffer->length);
+ continue;
+ }
+
+ // Re-map the buffer to the address space immediately after the buffer
+ vm_address_t virtualAddress = bufferAddress + buffer->length;
+ vm_prot_t cur_prot, max_prot;
+ result = vm_remap(mach_task_self(),
+ &virtualAddress, // mirror target
+ buffer->length, // size of mirror
+ 0, // auto alignment
+ 0, // force remapping to virtualAddress
+ mach_task_self(), // same task
+ bufferAddress, // mirror source
+ 0, // MAP READ-WRITE, NOT COPY
+ &cur_prot, // unused protection struct
+ &max_prot, // unused protection struct
+ VM_INHERIT_DEFAULT);
+ if (result != ERR_SUCCESS)
+ {
+ if (retries-- == 0)
+ {
+ reportResult(result, "Remap buffer memory");
+ return false;
+ }
+ // If this remap failed, we hit a race condition, so deallocate and try again
+ vm_deallocate(mach_task_self(), bufferAddress, buffer->length);
+ continue;
+ }
+
+ if (virtualAddress != bufferAddress + buffer->length)
+ {
+ // If the memory is not contiguous, clean up both allocated buffers and try again
+ if (retries-- == 0)
+ {
+ WLog_DBG(TAG, "Couldn't map buffer memory to end of buffer");
+ return false;
+ }
+
+ vm_deallocate(mach_task_self(), virtualAddress, buffer->length);
+ vm_deallocate(mach_task_self(), bufferAddress, buffer->length);
+ continue;
+ }
+
+ buffer->buffer = (void*)bufferAddress;
+ buffer->fillCount = 0;
+ buffer->head = buffer->tail = 0;
+
+ return true;
+ }
+ return false;
+}
+
+void TPCircularBufferCleanup(TPCircularBuffer* buffer)
+{
+ vm_deallocate(mach_task_self(), (vm_address_t)buffer->buffer, buffer->length * 2);
+ memset(buffer, 0, sizeof(TPCircularBuffer));
+}
+
+void TPCircularBufferClear(TPCircularBuffer* buffer)
+{
+ int32_t fillCount;
+ if (TPCircularBufferTail(buffer, &fillCount))
+ {
+ TPCircularBufferConsume(buffer, fillCount);
+ }
+}
diff --git a/channels/rdpsnd/client/ios/TPCircularBuffer.h b/channels/rdpsnd/client/ios/TPCircularBuffer.h
new file mode 100644
index 0000000..97e1095
--- /dev/null
+++ b/channels/rdpsnd/client/ios/TPCircularBuffer.h
@@ -0,0 +1,217 @@
+//
+// TPCircularBuffer.h
+// Circular/Ring buffer implementation
+//
+// https://github.com/michaeltyson/TPCircularBuffer
+//
+// Created by Michael Tyson on 10/12/2011.
+//
+//
+// This implementation makes use of a virtual memory mapping technique that inserts a virtual copy
+// of the buffer memory directly after the buffer's end, negating the need for any buffer
+// wrap-around logic. Clients can simply use the returned memory address as if it were contiguous
+// space.
+//
+// The implementation is thread-safe in the case of a single producer and single consumer.
+//
+// Virtual memory technique originally proposed by Philip Howard (http://vrb.slashusr.org/), and
+// adapted to Darwin by Kurt Revis (http://www.snoize.com,
+// http://www.snoize.com/Code/PlayBufferedSoundFile.tar.gz)
+//
+//
+// Copyright (C) 2012-2013 A Tasty Pixel
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+
+#ifndef TPCircularBuffer_h
+#define TPCircularBuffer_h
+
+#include <libkern/OSAtomic.h>
+#include <string.h>
+#include <winpr/assert.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ void* buffer;
+ int32_t length;
+ int32_t tail;
+ int32_t head;
+ volatile int32_t fillCount;
+ } TPCircularBuffer;
+
+ /*!
+ * Initialise buffer
+ *
+ * Note that the length is advisory only: Because of the way the
+ * memory mirroring technique works, the true buffer length will
+ * be multiples of the device page size (e.g. 4096 bytes)
+ *
+ * @param buffer Circular buffer
+ * @param length Length of buffer
+ */
+ bool TPCircularBufferInit(TPCircularBuffer* buffer, int32_t length);
+
+ /*!
+ * Cleanup buffer
+ *
+ * Releases buffer resources.
+ */
+ void TPCircularBufferCleanup(TPCircularBuffer* buffer);
+
+ /*!
+ * Clear buffer
+ *
+ * Resets buffer to original, empty state.
+ *
+ * This is safe for use by consumer while producer is accessing
+ * buffer.
+ */
+ void TPCircularBufferClear(TPCircularBuffer* buffer);
+
+ // Reading (consuming)
+
+ /*!
+ * Access end of buffer
+ *
+ * This gives you a pointer to the end of the buffer, ready
+ * for reading, and the number of available bytes to read.
+ *
+ * @param buffer Circular buffer
+ * @param availableBytes On output, the number of bytes ready for reading
+ * @return Pointer to the first bytes ready for reading, or NULL if buffer is empty
+ */
+ static __inline__ __attribute__((always_inline)) void*
+ TPCircularBufferTail(TPCircularBuffer* buffer, int32_t* availableBytes)
+ {
+ *availableBytes = buffer->fillCount;
+ if (*availableBytes == 0)
+ return NULL;
+ return (void*)((char*)buffer->buffer + buffer->tail);
+ }
+
+ /*!
+ * Consume bytes in buffer
+ *
+ * This frees up the just-read bytes, ready for writing again.
+ *
+ * @param buffer Circular buffer
+ * @param amount Number of bytes to consume
+ */
+ static __inline__ __attribute__((always_inline)) void
+ TPCircularBufferConsume(TPCircularBuffer* buffer, int32_t amount)
+ {
+ buffer->tail = (buffer->tail + amount) % buffer->length;
+ OSAtomicAdd32Barrier(-amount, &buffer->fillCount);
+ WINPR_ASSERT(buffer->fillCount >= 0);
+ }
+
+ /*!
+ * Version of TPCircularBufferConsume without the memory barrier, for more optimal use in
+ * single-threaded contexts
+ */
+ static __inline__ __attribute__((always_inline)) void
+ TPCircularBufferConsumeNoBarrier(TPCircularBuffer* buffer, int32_t amount)
+ {
+ buffer->tail = (buffer->tail + amount) % buffer->length;
+ buffer->fillCount -= amount;
+ WINPR_ASSERT(buffer->fillCount >= 0);
+ }
+
+ /*!
+ * Access front of buffer
+ *
+ * This gives you a pointer to the front of the buffer, ready
+ * for writing, and the number of available bytes to write.
+ *
+ * @param buffer Circular buffer
+ * @param availableBytes On output, the number of bytes ready for writing
+ * @return Pointer to the first bytes ready for writing, or NULL if buffer is full
+ */
+ static __inline__ __attribute__((always_inline)) void*
+ TPCircularBufferHead(TPCircularBuffer* buffer, int32_t* availableBytes)
+ {
+ *availableBytes = (buffer->length - buffer->fillCount);
+ if (*availableBytes == 0)
+ return NULL;
+ return (void*)((char*)buffer->buffer + buffer->head);
+ }
+
+ // Writing (producing)
+
+ /*!
+ * Produce bytes in buffer
+ *
+ * This marks the given section of the buffer ready for reading.
+ *
+ * @param buffer Circular buffer
+ * @param amount Number of bytes to produce
+ */
+ static __inline__ __attribute__((always_inline)) void
+ TPCircularBufferProduce(TPCircularBuffer* buffer, int amount)
+ {
+ buffer->head = (buffer->head + amount) % buffer->length;
+ OSAtomicAdd32Barrier(amount, &buffer->fillCount);
+ WINPR_ASSERT(buffer->fillCount <= buffer->length);
+ }
+
+ /*!
+ * Version of TPCircularBufferProduce without the memory barrier, for more optimal use in
+ * single-threaded contexts
+ */
+ static __inline__ __attribute__((always_inline)) void
+ TPCircularBufferProduceNoBarrier(TPCircularBuffer* buffer, int amount)
+ {
+ buffer->head = (buffer->head + amount) % buffer->length;
+ buffer->fillCount += amount;
+ WINPR_ASSERT(buffer->fillCount <= buffer->length);
+ }
+
+ /*!
+ * Helper routine to copy bytes to buffer
+ *
+ * This copies the given bytes to the buffer, and marks them ready for writing.
+ *
+ * @param buffer Circular buffer
+ * @param src Source buffer
+ * @param len Number of bytes in source buffer
+ * @return true if bytes copied, false if there was insufficient space
+ */
+ static __inline__ __attribute__((always_inline)) bool
+ TPCircularBufferProduceBytes(TPCircularBuffer* buffer, const void* src, int32_t len)
+ {
+ int32_t space;
+ void* ptr = TPCircularBufferHead(buffer, &space);
+ if (space < len)
+ return false;
+ memcpy(ptr, src, len);
+ TPCircularBufferProduce(buffer, len);
+ return true;
+ }
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/channels/rdpsnd/client/ios/rdpsnd_ios.c b/channels/rdpsnd/client/ios/rdpsnd_ios.c
new file mode 100644
index 0000000..b8f004f
--- /dev/null
+++ b/channels/rdpsnd/client/ios/rdpsnd_ios.c
@@ -0,0 +1,282 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Output Virtual Channel
+ *
+ * Copyright 2013 Dell Software <Mike.McDonald@software.dell.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/wtypes.h>
+
+#include <freerdp/types.h>
+#include <freerdp/codec/dsp.h>
+
+#import <AudioToolbox/AudioToolbox.h>
+
+#include "rdpsnd_main.h"
+#include "TPCircularBuffer.h"
+
+#define INPUT_BUFFER_SIZE 32768
+#define CIRCULAR_BUFFER_SIZE (INPUT_BUFFER_SIZE * 4)
+
+typedef struct
+{
+ rdpsndDevicePlugin device;
+ AudioComponentInstance audio_unit;
+ TPCircularBuffer buffer;
+ BOOL is_opened;
+ BOOL is_playing;
+} rdpsndIOSPlugin;
+
+#define THIS(__ptr) ((rdpsndIOSPlugin*)__ptr)
+
+static OSStatus rdpsnd_ios_render_cb(void* inRefCon,
+ AudioUnitRenderActionFlags __unused* ioActionFlags,
+ const AudioTimeStamp __unused* inTimeStamp, UInt32 inBusNumber,
+ UInt32 __unused inNumberFrames, AudioBufferList* ioData)
+{
+ if (inBusNumber != 0)
+ {
+ return noErr;
+ }
+
+ rdpsndIOSPlugin* p = THIS(inRefCon);
+
+ for (unsigned int i = 0; i < ioData->mNumberBuffers; i++)
+ {
+ AudioBuffer* target_buffer = &ioData->mBuffers[i];
+ int32_t available_bytes = 0;
+ const void* buffer = TPCircularBufferTail(&p->buffer, &available_bytes);
+
+ if (buffer != NULL && available_bytes > 0)
+ {
+ const int bytes_to_copy = MIN((int32_t)target_buffer->mDataByteSize, available_bytes);
+ memcpy(target_buffer->mData, buffer, bytes_to_copy);
+ target_buffer->mDataByteSize = bytes_to_copy;
+ TPCircularBufferConsume(&p->buffer, bytes_to_copy);
+ }
+ else
+ {
+ target_buffer->mDataByteSize = 0;
+ AudioOutputUnitStop(p->audio_unit);
+ p->is_playing = 0;
+ }
+ }
+
+ return noErr;
+}
+
+static BOOL rdpsnd_ios_format_supported(rdpsndDevicePlugin* __unused device,
+ const AUDIO_FORMAT* format)
+{
+ if (format->wFormatTag == WAVE_FORMAT_PCM)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+static BOOL rdpsnd_ios_set_volume(rdpsndDevicePlugin* __unused device, UINT32 __unused value)
+{
+ return TRUE;
+}
+
+static void rdpsnd_ios_start(rdpsndDevicePlugin* device)
+{
+ rdpsndIOSPlugin* p = THIS(device);
+
+ /* If this device is not playing... */
+ if (!p->is_playing)
+ {
+ /* Start the device. */
+ int32_t available_bytes = 0;
+ TPCircularBufferTail(&p->buffer, &available_bytes);
+
+ if (available_bytes > 0)
+ {
+ p->is_playing = 1;
+ AudioOutputUnitStart(p->audio_unit);
+ }
+ }
+}
+
+static void rdpsnd_ios_stop(rdpsndDevicePlugin* __unused device)
+{
+ rdpsndIOSPlugin* p = THIS(device);
+
+ /* If the device is playing... */
+ if (p->is_playing)
+ {
+ /* Stop the device. */
+ AudioOutputUnitStop(p->audio_unit);
+ p->is_playing = 0;
+ /* Free all buffers. */
+ TPCircularBufferClear(&p->buffer);
+ }
+}
+
+static UINT rdpsnd_ios_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
+{
+ rdpsndIOSPlugin* p = THIS(device);
+ const BOOL ok = TPCircularBufferProduceBytes(&p->buffer, data, size);
+
+ if (!ok)
+ return 0;
+
+ rdpsnd_ios_start(device);
+ return 10; /* TODO: Get real latencry in [ms] */
+}
+
+static BOOL rdpsnd_ios_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
+ int __unused latency)
+{
+ rdpsndIOSPlugin* p = THIS(device);
+
+ if (p->is_opened)
+ return TRUE;
+
+ /* Find the output audio unit. */
+ AudioComponentDescription desc;
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentType = kAudioUnitType_Output;
+ desc.componentSubType = kAudioUnitSubType_RemoteIO;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+ AudioComponent audioComponent = AudioComponentFindNext(NULL, &desc);
+
+ if (audioComponent == NULL)
+ return FALSE;
+
+ /* Open the audio unit. */
+ OSStatus status = AudioComponentInstanceNew(audioComponent, &p->audio_unit);
+
+ if (status != 0)
+ return FALSE;
+
+ /* Set the format for the AudioUnit. */
+ AudioStreamBasicDescription audioFormat = { 0 };
+ audioFormat.mSampleRate = format->nSamplesPerSec;
+ audioFormat.mFormatID = kAudioFormatLinearPCM;
+ audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
+ audioFormat.mFramesPerPacket = 1; /* imminent property of the Linear PCM */
+ audioFormat.mChannelsPerFrame = format->nChannels;
+ audioFormat.mBitsPerChannel = format->wBitsPerSample;
+ audioFormat.mBytesPerFrame = (format->wBitsPerSample * format->nChannels) / 8;
+ audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame * audioFormat.mFramesPerPacket;
+ status = AudioUnitSetProperty(p->audio_unit, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, 0, &audioFormat, sizeof(audioFormat));
+
+ if (status != 0)
+ {
+ AudioComponentInstanceDispose(p->audio_unit);
+ p->audio_unit = NULL;
+ return FALSE;
+ }
+
+ /* Set up the AudioUnit callback. */
+ AURenderCallbackStruct callbackStruct = { 0 };
+ callbackStruct.inputProc = rdpsnd_ios_render_cb;
+ callbackStruct.inputProcRefCon = p;
+ status =
+ AudioUnitSetProperty(p->audio_unit, kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Input, 0, &callbackStruct, sizeof(callbackStruct));
+
+ if (status != 0)
+ {
+ AudioComponentInstanceDispose(p->audio_unit);
+ p->audio_unit = NULL;
+ return FALSE;
+ }
+
+ /* Initialize the AudioUnit. */
+ status = AudioUnitInitialize(p->audio_unit);
+
+ if (status != 0)
+ {
+ AudioComponentInstanceDispose(p->audio_unit);
+ p->audio_unit = NULL;
+ return FALSE;
+ }
+
+ /* Allocate the circular buffer. */
+ const BOOL ok = TPCircularBufferInit(&p->buffer, CIRCULAR_BUFFER_SIZE);
+
+ if (!ok)
+ {
+ AudioUnitUninitialize(p->audio_unit);
+ AudioComponentInstanceDispose(p->audio_unit);
+ p->audio_unit = NULL;
+ return FALSE;
+ }
+
+ p->is_opened = 1;
+ return TRUE;
+}
+
+static void rdpsnd_ios_close(rdpsndDevicePlugin* device)
+{
+ rdpsndIOSPlugin* p = THIS(device);
+ /* Make sure the device is stopped. */
+ rdpsnd_ios_stop(device);
+
+ /* If the device is open... */
+ if (p->is_opened)
+ {
+ /* Close the device. */
+ AudioUnitUninitialize(p->audio_unit);
+ AudioComponentInstanceDispose(p->audio_unit);
+ p->audio_unit = NULL;
+ p->is_opened = 0;
+ /* Destroy the circular buffer. */
+ TPCircularBufferCleanup(&p->buffer);
+ }
+}
+
+static void rdpsnd_ios_free(rdpsndDevicePlugin* device)
+{
+ rdpsndIOSPlugin* p = THIS(device);
+ /* Ensure the device is closed. */
+ rdpsnd_ios_close(device);
+ /* Free memory associated with the device. */
+ free(p);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT ios_freerdp_rdpsnd_client_subsystem_entry(
+ PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ rdpsndIOSPlugin* p = (rdpsndIOSPlugin*)calloc(1, sizeof(rdpsndIOSPlugin));
+
+ if (!p)
+ return CHANNEL_RC_NO_MEMORY;
+
+ p->device.Open = rdpsnd_ios_open;
+ p->device.FormatSupported = rdpsnd_ios_format_supported;
+ p->device.SetVolume = rdpsnd_ios_set_volume;
+ p->device.Play = rdpsnd_ios_play;
+ p->device.Start = rdpsnd_ios_start;
+ p->device.Close = rdpsnd_ios_close;
+ p->device.Free = rdpsnd_ios_free;
+ pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)p);
+ return CHANNEL_RC_OK;
+}
diff --git a/channels/rdpsnd/client/mac/CMakeLists.txt b/channels/rdpsnd/client/mac/CMakeLists.txt
new file mode 100644
index 0000000..4bcf1bc
--- /dev/null
+++ b/channels/rdpsnd/client/mac/CMakeLists.txt
@@ -0,0 +1,44 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Laxmikant Rashinkar <LK.Rashinkar@gmail.com>
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+# Copyright 2013 Corey Clayton <can.of.tuna@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("rdpsnd" "mac" "")
+
+find_library(COCOA_LIBRARY Cocoa REQUIRED)
+FIND_LIBRARY(CORE_FOUNDATION CoreFoundation)
+FIND_LIBRARY(CORE_AUDIO CoreAudio REQUIRED)
+FIND_LIBRARY(AUDIO_TOOL AudioToolbox REQUIRED)
+FIND_LIBRARY(AV_FOUNDATION AVFoundation REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ rdpsnd_mac.m)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${AUDIO_TOOL}
+ ${AV_FOUNDATION}
+ ${CORE_AUDIO}
+ ${COCOA_LIBRARY}
+ ${CORE_FOUNDATION}
+)
+
+include_directories(..)
+include_directories(${MACAUDIO_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/rdpsnd/client/mac/rdpsnd_mac.m b/channels/rdpsnd/client/mac/rdpsnd_mac.m
new file mode 100644
index 0000000..648ded4
--- /dev/null
+++ b/channels/rdpsnd/client/mac/rdpsnd_mac.m
@@ -0,0 +1,402 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Output Virtual Channel
+ *
+ * Copyright 2012 Laxmikant Rashinkar <LK.Rashinkar@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 Inuvika Inc.
+ * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/sysinfo.h>
+
+#include <freerdp/types.h>
+
+#include <AVFoundation/AVAudioBuffer.h>
+#include <AVFoundation/AVFoundation.h>
+
+#include "rdpsnd_main.h"
+
+typedef struct
+{
+ rdpsndDevicePlugin device;
+
+ BOOL isOpen;
+ BOOL isPlaying;
+
+ UINT32 latency;
+ AUDIO_FORMAT format;
+
+ AVAudioEngine *engine;
+ AVAudioPlayerNode *player;
+ UINT64 diff;
+} rdpsndMacPlugin;
+
+static BOOL rdpsnd_mac_set_format(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format,
+ UINT32 latency)
+{
+ rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
+ if (!mac || !format)
+ return FALSE;
+
+ mac->latency = latency;
+ mac->format = *format;
+
+ audio_format_print(WLog_Get(TAG), WLOG_DEBUG, format);
+ return TRUE;
+}
+
+static char *FormatError(OSStatus st)
+{
+ switch (st)
+ {
+ case kAudioFileUnspecifiedError:
+ return "kAudioFileUnspecifiedError";
+
+ case kAudioFileUnsupportedFileTypeError:
+ return "kAudioFileUnsupportedFileTypeError";
+
+ case kAudioFileUnsupportedDataFormatError:
+ return "kAudioFileUnsupportedDataFormatError";
+
+ case kAudioFileUnsupportedPropertyError:
+ return "kAudioFileUnsupportedPropertyError";
+
+ case kAudioFileBadPropertySizeError:
+ return "kAudioFileBadPropertySizeError";
+
+ case kAudioFilePermissionsError:
+ return "kAudioFilePermissionsError";
+
+ case kAudioFileNotOptimizedError:
+ return "kAudioFileNotOptimizedError";
+
+ case kAudioFileInvalidChunkError:
+ return "kAudioFileInvalidChunkError";
+
+ case kAudioFileDoesNotAllow64BitDataSizeError:
+ return "kAudioFileDoesNotAllow64BitDataSizeError";
+
+ case kAudioFileInvalidPacketOffsetError:
+ return "kAudioFileInvalidPacketOffsetError";
+
+ case kAudioFileInvalidFileError:
+ return "kAudioFileInvalidFileError";
+
+ case kAudioFileOperationNotSupportedError:
+ return "kAudioFileOperationNotSupportedError";
+
+ case kAudioFileNotOpenError:
+ return "kAudioFileNotOpenError";
+
+ case kAudioFileEndOfFileError:
+ return "kAudioFileEndOfFileError";
+
+ case kAudioFilePositionError:
+ return "kAudioFilePositionError";
+
+ case kAudioFileFileNotFoundError:
+ return "kAudioFileFileNotFoundError";
+
+ default:
+ return "unknown error";
+ }
+}
+
+static void rdpsnd_mac_release(rdpsndMacPlugin *mac)
+{
+ if (mac->player)
+ [mac->player release];
+ mac->player = NULL;
+
+ if (mac->engine)
+ [mac->engine release];
+ mac->engine = NULL;
+}
+
+static BOOL rdpsnd_mac_open(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format, UINT32 latency)
+{
+ @autoreleasepool
+ {
+ AudioDeviceID outputDeviceID;
+ UInt32 propertySize;
+ OSStatus err;
+ NSError *error;
+ rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
+ AudioObjectPropertyAddress propertyAddress = {
+ kAudioHardwarePropertyDefaultOutputDevice,
+ kAudioObjectPropertyScopeGlobal,
+#if defined(MAC_OS_VERSION_12_0)
+ kAudioObjectPropertyElementMain
+#else
+ kAudioObjectPropertyElementMaster
+#endif
+ };
+
+ if (mac->isOpen)
+ return TRUE;
+
+ if (!rdpsnd_mac_set_format(device, format, latency))
+ return FALSE;
+
+ propertySize = sizeof(outputDeviceID);
+ err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL,
+ &propertySize, &outputDeviceID);
+ if (err)
+ {
+ WLog_ERR(TAG, "AudioHardwareGetProperty: %s", FormatError(err));
+ return FALSE;
+ }
+
+ mac->engine = [[AVAudioEngine alloc] init];
+ if (!mac->engine)
+ return FALSE;
+
+ err = AudioUnitSetProperty(mac->engine.outputNode.audioUnit,
+ kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global,
+ 0, &outputDeviceID, sizeof(outputDeviceID));
+ if (err)
+ {
+ rdpsnd_mac_release(mac);
+ WLog_ERR(TAG, "AudioUnitSetProperty: %s", FormatError(err));
+ return FALSE;
+ }
+
+ mac->player = [[AVAudioPlayerNode alloc] init];
+ if (!mac->player)
+ {
+ rdpsnd_mac_release(mac);
+ WLog_ERR(TAG, "AVAudioPlayerNode::init() failed");
+ return FALSE;
+ }
+
+ [mac->engine attachNode:mac->player];
+
+ [mac->engine connect:mac->player to:mac->engine.mainMixerNode format:nil];
+
+ [mac->engine prepare];
+
+ if (![mac->engine startAndReturnError:&error])
+ {
+ device->Close(device);
+ WLog_ERR(TAG, "Failed to start audio player %s",
+ [error.localizedDescription UTF8String]);
+ return FALSE;
+ }
+
+ mac->isOpen = TRUE;
+ return TRUE;
+ }
+}
+
+static void rdpsnd_mac_close(rdpsndDevicePlugin *device)
+{
+ @autoreleasepool
+ {
+ rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
+
+ if (mac->isPlaying)
+ {
+ [mac->player stop];
+ mac->isPlaying = FALSE;
+ }
+
+ if (mac->isOpen)
+ {
+ [mac->engine stop];
+ mac->isOpen = FALSE;
+ }
+
+ rdpsnd_mac_release(mac);
+ }
+}
+
+static void rdpsnd_mac_free(rdpsndDevicePlugin *device)
+{
+ rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
+ device->Close(device);
+ free(mac);
+}
+
+static BOOL rdpsnd_mac_format_supported(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format)
+{
+ WINPR_UNUSED(device);
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ if (format->wBitsPerSample != 16)
+ return FALSE;
+
+ if (format->nChannels != 2)
+ return FALSE;
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+}
+
+static BOOL rdpsnd_mac_set_volume(rdpsndDevicePlugin *device, UINT32 value)
+{
+ @autoreleasepool
+ {
+ Float32 fVolume;
+ UINT16 volumeLeft;
+ UINT16 volumeRight;
+ rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
+
+ if (!mac->player)
+ return FALSE;
+
+ volumeLeft = (value & 0xFFFF);
+ volumeRight = ((value >> 16) & 0xFFFF);
+ fVolume = ((float)volumeLeft) / 65535.0f;
+
+ mac->player.volume = fVolume;
+
+ return TRUE;
+ }
+}
+
+static void rdpsnd_mac_start(rdpsndDevicePlugin *device)
+{
+ @autoreleasepool
+ {
+ rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
+
+ if (!mac->isPlaying)
+ {
+ if (!mac->engine.isRunning)
+ {
+ NSError *error;
+
+ if (![mac->engine startAndReturnError:&error])
+ {
+ device->Close(device);
+ WLog_ERR(TAG, "Failed to start audio player %s",
+ [error.localizedDescription UTF8String]);
+ return;
+ }
+ }
+
+ [mac->player play];
+
+ mac->isPlaying = TRUE;
+ mac->diff = 100; /* Initial latency, corrected after first sample is played. */
+ }
+ }
+}
+
+static UINT rdpsnd_mac_play(rdpsndDevicePlugin *device, const BYTE *data, size_t size)
+{
+ @autoreleasepool
+ {
+ rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
+ AVAudioPCMBuffer *buffer;
+ AVAudioFormat *format;
+ float *const *db;
+ size_t step;
+ AVAudioFrameCount count;
+ UINT64 start = GetTickCount64();
+
+ if (!mac->isOpen)
+ return 0;
+
+ step = 2 * mac->format.nChannels;
+
+ count = size / step;
+ format = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
+ sampleRate:mac->format.nSamplesPerSec
+ channels:mac->format.nChannels
+ interleaved:NO];
+
+ if (!format)
+ {
+ WLog_WARN(TAG, "AVAudioFormat::init() failed");
+ return 0;
+ }
+
+ buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:format frameCapacity:count];
+ [format release];
+
+ if (!buffer)
+ {
+ WLog_WARN(TAG, "AVAudioPCMBuffer::init() failed");
+ return 0;
+ }
+
+ buffer.frameLength = buffer.frameCapacity;
+ db = buffer.floatChannelData;
+
+ for (size_t pos = 0; pos < count; pos++)
+ {
+ const BYTE *d = &data[pos * step];
+ for (size_t x = 0; x < mac->format.nChannels; x++)
+ {
+ const float val = (int16_t)((uint16_t)d[0] | ((uint16_t)d[1] << 8)) / 32768.0f;
+ db[x][pos] = val;
+ d += sizeof(int16_t);
+ }
+ }
+
+ rdpsnd_mac_start(device);
+
+ [mac->player scheduleBuffer:buffer
+ completionHandler:^{
+ UINT64 stop = GetTickCount64();
+ if (start > stop)
+ mac->diff = 0;
+ else
+ mac->diff = stop - start;
+ }];
+
+ [buffer release];
+
+ return mac->diff > UINT_MAX ? UINT_MAX : mac->diff;
+ }
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT mac_freerdp_rdpsnd_client_subsystem_entry(
+ PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ rdpsndMacPlugin *mac;
+ mac = (rdpsndMacPlugin *)calloc(1, sizeof(rdpsndMacPlugin));
+
+ if (!mac)
+ return CHANNEL_RC_NO_MEMORY;
+
+ mac->device.Open = rdpsnd_mac_open;
+ mac->device.FormatSupported = rdpsnd_mac_format_supported;
+ mac->device.SetVolume = rdpsnd_mac_set_volume;
+ mac->device.Play = rdpsnd_mac_play;
+ mac->device.Close = rdpsnd_mac_close;
+ mac->device.Free = rdpsnd_mac_free;
+ pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin *)mac);
+ return CHANNEL_RC_OK;
+}
diff --git a/channels/rdpsnd/client/opensles/CMakeLists.txt b/channels/rdpsnd/client/opensles/CMakeLists.txt
new file mode 100644
index 0000000..1df65e9
--- /dev/null
+++ b/channels/rdpsnd/client/opensles/CMakeLists.txt
@@ -0,0 +1,35 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2013 Armin Novak <armin.novak@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("rdpsnd" "opensles" "")
+
+find_package(OpenSLES REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ opensl_io.c
+ rdpsnd_opensles.c)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${OpenSLES_LIBRARIES}
+)
+
+include_directories(..)
+include_directories(${OpenSLES_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/rdpsnd/client/opensles/opensl_io.c b/channels/rdpsnd/client/opensles/opensl_io.c
new file mode 100644
index 0000000..70da341
--- /dev/null
+++ b/channels/rdpsnd/client/opensles/opensl_io.c
@@ -0,0 +1,422 @@
+/*
+opensl_io.c:
+Android OpenSL input/output module
+Copyright (c) 2012, Victor Lazzarini
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the <organization> nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <winpr/assert.h>
+
+#include "rdpsnd_main.h"
+#include "opensl_io.h"
+#define CONV16BIT 32768
+#define CONVMYFLT (1. / 32768.)
+
+static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context);
+
+// creates the OpenSL ES audio engine
+static SLresult openSLCreateEngine(OPENSL_STREAM* p)
+{
+ SLresult result;
+ // create engine
+ result = slCreateEngine(&(p->engineObject), 0, NULL, 0, NULL, NULL);
+ DEBUG_SND("engineObject=%p", (void*)p->engineObject);
+
+ if (result != SL_RESULT_SUCCESS)
+ goto engine_end;
+
+ // realize the engine
+ result = (*p->engineObject)->Realize(p->engineObject, SL_BOOLEAN_FALSE);
+ DEBUG_SND("Realize=%" PRIu32 "", result);
+
+ if (result != SL_RESULT_SUCCESS)
+ goto engine_end;
+
+ // get the engine interface, which is needed in order to create other objects
+ result = (*p->engineObject)->GetInterface(p->engineObject, SL_IID_ENGINE, &(p->engineEngine));
+ DEBUG_SND("engineEngine=%p", (void*)p->engineEngine);
+
+ if (result != SL_RESULT_SUCCESS)
+ goto engine_end;
+
+engine_end:
+ return result;
+}
+
+// opens the OpenSL ES device for output
+static SLresult openSLPlayOpen(OPENSL_STREAM* p)
+{
+ SLresult result;
+ SLuint32 sr = p->sr;
+ SLuint32 channels = p->outchannels;
+ WINPR_ASSERT(p->engineObject);
+ WINPR_ASSERT(p->engineEngine);
+
+ if (channels)
+ {
+ // configure audio source
+ SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
+ p->queuesize };
+
+ switch (sr)
+ {
+ case 8000:
+ sr = SL_SAMPLINGRATE_8;
+ break;
+
+ case 11025:
+ sr = SL_SAMPLINGRATE_11_025;
+ break;
+
+ case 16000:
+ sr = SL_SAMPLINGRATE_16;
+ break;
+
+ case 22050:
+ sr = SL_SAMPLINGRATE_22_05;
+ break;
+
+ case 24000:
+ sr = SL_SAMPLINGRATE_24;
+ break;
+
+ case 32000:
+ sr = SL_SAMPLINGRATE_32;
+ break;
+
+ case 44100:
+ sr = SL_SAMPLINGRATE_44_1;
+ break;
+
+ case 48000:
+ sr = SL_SAMPLINGRATE_48;
+ break;
+
+ case 64000:
+ sr = SL_SAMPLINGRATE_64;
+ break;
+
+ case 88200:
+ sr = SL_SAMPLINGRATE_88_2;
+ break;
+
+ case 96000:
+ sr = SL_SAMPLINGRATE_96;
+ break;
+
+ case 192000:
+ sr = SL_SAMPLINGRATE_192;
+ break;
+
+ default:
+ return -1;
+ }
+
+ const SLInterfaceID ids[] = { SL_IID_VOLUME };
+ const SLboolean req[] = { SL_BOOLEAN_FALSE };
+ result = (*p->engineEngine)
+ ->CreateOutputMix(p->engineEngine, &(p->outputMixObject), 1, ids, req);
+ DEBUG_SND("engineEngine=%p", (void*)p->engineEngine);
+ WINPR_ASSERT(!result);
+
+ if (result != SL_RESULT_SUCCESS)
+ goto end_openaudio;
+
+ // realize the output mix
+ result = (*p->outputMixObject)->Realize(p->outputMixObject, SL_BOOLEAN_FALSE);
+ DEBUG_SND("Realize=%" PRIu32 "", result);
+ WINPR_ASSERT(!result);
+
+ if (result != SL_RESULT_SUCCESS)
+ goto end_openaudio;
+
+ int speakers;
+
+ if (channels > 1)
+ speakers = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+ else
+ speakers = SL_SPEAKER_FRONT_CENTER;
+
+ SLDataFormat_PCM format_pcm = { SL_DATAFORMAT_PCM,
+ channels,
+ sr,
+ SL_PCMSAMPLEFORMAT_FIXED_16,
+ SL_PCMSAMPLEFORMAT_FIXED_16,
+ speakers,
+ SL_BYTEORDER_LITTLEENDIAN };
+ SLDataSource audioSrc = { &loc_bufq, &format_pcm };
+ // configure audio sink
+ SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, p->outputMixObject };
+ SLDataSink audioSnk = { &loc_outmix, NULL };
+ // create audio player
+ const SLInterfaceID ids1[] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME };
+ const SLboolean req1[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
+ result = (*p->engineEngine)
+ ->CreateAudioPlayer(p->engineEngine, &(p->bqPlayerObject), &audioSrc,
+ &audioSnk, 2, ids1, req1);
+ DEBUG_SND("bqPlayerObject=%p", (void*)p->bqPlayerObject);
+ WINPR_ASSERT(!result);
+
+ if (result != SL_RESULT_SUCCESS)
+ goto end_openaudio;
+
+ // realize the player
+ result = (*p->bqPlayerObject)->Realize(p->bqPlayerObject, SL_BOOLEAN_FALSE);
+ DEBUG_SND("Realize=%" PRIu32 "", result);
+ WINPR_ASSERT(!result);
+
+ if (result != SL_RESULT_SUCCESS)
+ goto end_openaudio;
+
+ // get the play interface
+ result =
+ (*p->bqPlayerObject)->GetInterface(p->bqPlayerObject, SL_IID_PLAY, &(p->bqPlayerPlay));
+ DEBUG_SND("bqPlayerPlay=%p", (void*)p->bqPlayerPlay);
+ WINPR_ASSERT(!result);
+
+ if (result != SL_RESULT_SUCCESS)
+ goto end_openaudio;
+
+ // get the volume interface
+ result = (*p->bqPlayerObject)
+ ->GetInterface(p->bqPlayerObject, SL_IID_VOLUME, &(p->bqPlayerVolume));
+ DEBUG_SND("bqPlayerVolume=%p", (void*)p->bqPlayerVolume);
+ WINPR_ASSERT(!result);
+
+ if (result != SL_RESULT_SUCCESS)
+ goto end_openaudio;
+
+ // get the buffer queue interface
+ result = (*p->bqPlayerObject)
+ ->GetInterface(p->bqPlayerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ &(p->bqPlayerBufferQueue));
+ DEBUG_SND("bqPlayerBufferQueue=%p", (void*)p->bqPlayerBufferQueue);
+ WINPR_ASSERT(!result);
+
+ if (result != SL_RESULT_SUCCESS)
+ goto end_openaudio;
+
+ // register callback on the buffer queue
+ result = (*p->bqPlayerBufferQueue)
+ ->RegisterCallback(p->bqPlayerBufferQueue, bqPlayerCallback, p);
+ DEBUG_SND("bqPlayerCallback=%p", (void*)p->bqPlayerCallback);
+ WINPR_ASSERT(!result);
+
+ if (result != SL_RESULT_SUCCESS)
+ goto end_openaudio;
+
+ // set the player's state to playing
+ result = (*p->bqPlayerPlay)->SetPlayState(p->bqPlayerPlay, SL_PLAYSTATE_PLAYING);
+ DEBUG_SND("SetPlayState=%" PRIu32 "", result);
+ WINPR_ASSERT(!result);
+ end_openaudio:
+ WINPR_ASSERT(!result);
+ return result;
+ }
+
+ return SL_RESULT_SUCCESS;
+}
+
+// close the OpenSL IO and destroy the audio engine
+static void openSLDestroyEngine(OPENSL_STREAM* p)
+{
+ // destroy buffer queue audio player object, and invalidate all associated interfaces
+ if (p->bqPlayerObject != NULL)
+ {
+ (*p->bqPlayerObject)->Destroy(p->bqPlayerObject);
+ p->bqPlayerObject = NULL;
+ p->bqPlayerVolume = NULL;
+ p->bqPlayerPlay = NULL;
+ p->bqPlayerBufferQueue = NULL;
+ p->bqPlayerEffectSend = NULL;
+ }
+
+ // destroy output mix object, and invalidate all associated interfaces
+ if (p->outputMixObject != NULL)
+ {
+ (*p->outputMixObject)->Destroy(p->outputMixObject);
+ p->outputMixObject = NULL;
+ }
+
+ // destroy engine object, and invalidate all associated interfaces
+ if (p->engineObject != NULL)
+ {
+ (*p->engineObject)->Destroy(p->engineObject);
+ p->engineObject = NULL;
+ p->engineEngine = NULL;
+ }
+}
+
+// open the android audio device for and/or output
+OPENSL_STREAM* android_OpenAudioDevice(int sr, int outchannels, int bufferframes)
+{
+ OPENSL_STREAM* p;
+ p = (OPENSL_STREAM*)calloc(1, sizeof(OPENSL_STREAM));
+
+ if (!p)
+ return NULL;
+
+ p->queuesize = bufferframes;
+ p->outchannels = outchannels;
+ p->sr = sr;
+
+ if (openSLCreateEngine(p) != SL_RESULT_SUCCESS)
+ {
+ android_CloseAudioDevice(p);
+ return NULL;
+ }
+
+ if (openSLPlayOpen(p) != SL_RESULT_SUCCESS)
+ {
+ android_CloseAudioDevice(p);
+ return NULL;
+ }
+
+ p->queue = Queue_New(TRUE, -1, -1);
+
+ if (!p->queue)
+ {
+ android_CloseAudioDevice(p);
+ return NULL;
+ }
+
+ return p;
+}
+
+// close the android audio device
+void android_CloseAudioDevice(OPENSL_STREAM* p)
+{
+ if (p == NULL)
+ return;
+
+ openSLDestroyEngine(p);
+
+ if (p->queue)
+ Queue_Free(p->queue);
+
+ free(p);
+}
+
+// this callback handler is called every time a buffer finishes playing
+static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
+{
+ OPENSL_STREAM* p = (OPENSL_STREAM*)context;
+ WINPR_ASSERT(p);
+ WINPR_ASSERT(p->queue);
+ void* data = Queue_Dequeue(p->queue);
+ free(data);
+}
+
+// puts a buffer of size samples to the device
+int android_AudioOut(OPENSL_STREAM* p, const short* buffer, int size)
+{
+ HANDLE ev;
+ WINPR_ASSERT(p);
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(size > 0);
+
+ ev = Queue_Event(p->queue);
+ /* Assure, that the queue is not full. */
+ if (p->queuesize <= Queue_Count(p->queue) && WaitForSingleObject(ev, INFINITE) == WAIT_FAILED)
+ {
+ DEBUG_SND("WaitForSingleObject failed!");
+ return -1;
+ }
+
+ void* data = calloc(size, sizeof(short));
+
+ if (!data)
+ {
+ DEBUG_SND("unable to allocate a buffer");
+ return -1;
+ }
+
+ memcpy(data, buffer, size * sizeof(short));
+ Queue_Enqueue(p->queue, data);
+ (*p->bqPlayerBufferQueue)->Enqueue(p->bqPlayerBufferQueue, data, sizeof(short) * size);
+ return size;
+}
+
+int android_GetOutputMute(OPENSL_STREAM* p)
+{
+ SLboolean mute;
+ WINPR_ASSERT(p);
+ WINPR_ASSERT(p->bqPlayerVolume);
+ SLresult rc = (*p->bqPlayerVolume)->GetMute(p->bqPlayerVolume, &mute);
+
+ if (SL_RESULT_SUCCESS != rc)
+ return SL_BOOLEAN_FALSE;
+
+ return mute;
+}
+
+BOOL android_SetOutputMute(OPENSL_STREAM* p, BOOL _mute)
+{
+ SLboolean mute = _mute;
+ WINPR_ASSERT(p);
+ WINPR_ASSERT(p->bqPlayerVolume);
+ SLresult rc = (*p->bqPlayerVolume)->SetMute(p->bqPlayerVolume, mute);
+
+ if (SL_RESULT_SUCCESS != rc)
+ return FALSE;
+
+ return TRUE;
+}
+
+int android_GetOutputVolume(OPENSL_STREAM* p)
+{
+ SLmillibel level;
+ WINPR_ASSERT(p);
+ WINPR_ASSERT(p->bqPlayerVolume);
+ SLresult rc = (*p->bqPlayerVolume)->GetVolumeLevel(p->bqPlayerVolume, &level);
+
+ if (SL_RESULT_SUCCESS != rc)
+ return 0;
+
+ return level;
+}
+
+int android_GetOutputVolumeMax(OPENSL_STREAM* p)
+{
+ SLmillibel level;
+ WINPR_ASSERT(p);
+ WINPR_ASSERT(p->bqPlayerVolume);
+ SLresult rc = (*p->bqPlayerVolume)->GetMaxVolumeLevel(p->bqPlayerVolume, &level);
+
+ if (SL_RESULT_SUCCESS != rc)
+ return 0;
+
+ return level;
+}
+
+BOOL android_SetOutputVolume(OPENSL_STREAM* p, int level)
+{
+ SLresult rc = (*p->bqPlayerVolume)->SetVolumeLevel(p->bqPlayerVolume, level);
+
+ if (SL_RESULT_SUCCESS != rc)
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/channels/rdpsnd/client/opensles/opensl_io.h b/channels/rdpsnd/client/opensles/opensl_io.h
new file mode 100644
index 0000000..f303d21
--- /dev/null
+++ b/channels/rdpsnd/client/opensles/opensl_io.h
@@ -0,0 +1,110 @@
+/*
+opensl_io.c:
+Android OpenSL input/output module header
+Copyright (c) 2012, Victor Lazzarini
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the <organization> nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef FREERDP_CHANNEL_RDPSND_CLIENT_OPENSL_IO_H
+#define FREERDP_CHANNEL_RDPSND_CLIENT_OPENSL_IO_H
+
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+#include <stdlib.h>
+#include <winpr/synch.h>
+
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ // engine interfaces
+ SLObjectItf engineObject;
+ SLEngineItf engineEngine;
+
+ // output mix interfaces
+ SLObjectItf outputMixObject;
+
+ // buffer queue player interfaces
+ SLObjectItf bqPlayerObject;
+ SLPlayItf bqPlayerPlay;
+ SLVolumeItf bqPlayerVolume;
+ SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue;
+ SLEffectSendItf bqPlayerEffectSend;
+
+ unsigned int outchannels;
+ unsigned int sr;
+
+ unsigned int queuesize;
+ wQueue* queue;
+ } OPENSL_STREAM;
+
+ /*
+ Open the audio device with a given sampling rate (sr), output channels and IO buffer size
+ in frames. Returns a handle to the OpenSL stream
+ */
+ FREERDP_LOCAL OPENSL_STREAM* android_OpenAudioDevice(int sr, int outchannels, int bufferframes);
+ /*
+ Close the audio device
+ */
+ FREERDP_LOCAL void android_CloseAudioDevice(OPENSL_STREAM* p);
+ /*
+ Write a buffer to the OpenSL stream *p, of size samples. Returns the number of samples written.
+ */
+ FREERDP_LOCAL int android_AudioOut(OPENSL_STREAM* p, const short* buffer, int size);
+ /*
+ * Set the volume input level.
+ */
+ FREERDP_LOCAL void android_SetInputVolume(OPENSL_STREAM* p, int level);
+ /*
+ * Get the current output mute setting.
+ */
+ FREERDP_LOCAL int android_GetOutputMute(OPENSL_STREAM* p);
+ /*
+ * Change the current output mute setting.
+ */
+ FREERDP_LOCAL BOOL android_SetOutputMute(OPENSL_STREAM* p, BOOL mute);
+ /*
+ * Get the current output volume level.
+ */
+ FREERDP_LOCAL int android_GetOutputVolume(OPENSL_STREAM* p);
+ /*
+ * Get the maximum output volume level.
+ */
+ FREERDP_LOCAL int android_GetOutputVolumeMax(OPENSL_STREAM* p);
+
+ /*
+ * Set the volume output level.
+ */
+ FREERDP_LOCAL BOOL android_SetOutputVolume(OPENSL_STREAM* p, int level);
+#ifdef __cplusplus
+};
+#endif
+
+#endif /* FREERDP_CHANNEL_RDPSND_CLIENT_OPENSL_IO_H */
diff --git a/channels/rdpsnd/client/opensles/rdpsnd_opensles.c b/channels/rdpsnd/client/opensles/rdpsnd_opensles.c
new file mode 100644
index 0000000..c5679b6
--- /dev/null
+++ b/channels/rdpsnd/client/opensles/rdpsnd_opensles.c
@@ -0,0 +1,378 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Output Virtual Channel
+ *
+ * Copyright 2013 Armin Novak <armin.novak@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include <winpr/crt.h>
+#include <winpr/cmdline.h>
+#include <winpr/sysinfo.h>
+#include <winpr/collections.h>
+
+#include <freerdp/types.h>
+#include <freerdp/channels/log.h>
+
+#include "opensl_io.h"
+#include "rdpsnd_main.h"
+
+typedef struct
+{
+ rdpsndDevicePlugin device;
+
+ UINT32 latency;
+ int wformat;
+ int block_size;
+ char* device_name;
+
+ OPENSL_STREAM* stream;
+
+ UINT32 volume;
+
+ UINT32 rate;
+ UINT32 channels;
+ int format;
+} rdpsndopenslesPlugin;
+
+static int rdpsnd_opensles_volume_to_millibel(unsigned short level, int max)
+{
+ const int min = SL_MILLIBEL_MIN;
+ const int step = max - min;
+ const int rc = (level * step / 0xFFFF) + min;
+ DEBUG_SND("level=%hu, min=%d, max=%d, step=%d, result=%d", level, min, max, step, rc);
+ return rc;
+}
+
+static unsigned short rdpsnd_opensles_millibel_to_volume(int millibel, int max)
+{
+ const int min = SL_MILLIBEL_MIN;
+ const int range = max - min;
+ const int rc = ((millibel - min) * 0xFFFF + range / 2 + 1) / range;
+ DEBUG_SND("millibel=%d, min=%d, max=%d, range=%d, result=%d", millibel, min, max, range, rc);
+ return rc;
+}
+
+static bool rdpsnd_opensles_check_handle(const rdpsndopenslesPlugin* hdl)
+{
+ bool rc = true;
+
+ if (!hdl)
+ rc = false;
+ else
+ {
+ if (!hdl->stream)
+ rc = false;
+ }
+
+ return rc;
+}
+
+static BOOL rdpsnd_opensles_set_volume(rdpsndDevicePlugin* device, UINT32 volume);
+
+static int rdpsnd_opensles_set_params(rdpsndopenslesPlugin* opensles)
+{
+ DEBUG_SND("opensles=%p", (void*)opensles);
+
+ if (!rdpsnd_opensles_check_handle(opensles))
+ return 0;
+
+ if (opensles->stream)
+ android_CloseAudioDevice(opensles->stream);
+
+ opensles->stream = android_OpenAudioDevice(opensles->rate, opensles->channels, 20);
+ return 0;
+}
+
+static BOOL rdpsnd_opensles_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
+ UINT32 latency)
+{
+ rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
+ rdpsnd_opensles_check_handle(opensles);
+ DEBUG_SND("opensles=%p format=%p, latency=%" PRIu32, (void*)opensles, (void*)format, latency);
+
+ if (format)
+ {
+ DEBUG_SND("format=%" PRIu16 ", cbsize=%" PRIu16 ", samples=%" PRIu32 ", bits=%" PRIu16
+ ", channels=%" PRIu16 ", align=%" PRIu16 "",
+ format->wFormatTag, format->cbSize, format->nSamplesPerSec,
+ format->wBitsPerSample, format->nChannels, format->nBlockAlign);
+ opensles->rate = format->nSamplesPerSec;
+ opensles->channels = format->nChannels;
+ opensles->format = format->wFormatTag;
+ opensles->wformat = format->wFormatTag;
+ opensles->block_size = format->nBlockAlign;
+ }
+
+ opensles->latency = latency;
+ return (rdpsnd_opensles_set_params(opensles) == 0);
+}
+
+static BOOL rdpsnd_opensles_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
+ UINT32 latency)
+{
+ rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
+ DEBUG_SND("opensles=%p format=%p, latency=%" PRIu32 ", rate=%" PRIu32 "", (void*)opensles,
+ (void*)format, latency, opensles->rate);
+
+ if (rdpsnd_opensles_check_handle(opensles))
+ return TRUE;
+
+ opensles->stream = android_OpenAudioDevice(opensles->rate, opensles->channels, 20);
+ WINPR_ASSERT(opensles->stream);
+
+ if (!opensles->stream)
+ WLog_ERR(TAG, "android_OpenAudioDevice failed");
+ else
+ rdpsnd_opensles_set_volume(device, opensles->volume);
+
+ return rdpsnd_opensles_set_format(device, format, latency);
+}
+
+static void rdpsnd_opensles_close(rdpsndDevicePlugin* device)
+{
+ rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
+ DEBUG_SND("opensles=%p", (void*)opensles);
+
+ if (!rdpsnd_opensles_check_handle(opensles))
+ return;
+
+ android_CloseAudioDevice(opensles->stream);
+ opensles->stream = NULL;
+}
+
+static void rdpsnd_opensles_free(rdpsndDevicePlugin* device)
+{
+ rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
+ DEBUG_SND("opensles=%p", (void*)opensles);
+ WINPR_ASSERT(opensles);
+ WINPR_ASSERT(opensles->device_name);
+ free(opensles->device_name);
+ free(opensles);
+}
+
+static BOOL rdpsnd_opensles_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
+{
+ DEBUG_SND("format=%" PRIu16 ", cbsize=%" PRIu16 ", samples=%" PRIu32 ", bits=%" PRIu16
+ ", channels=%" PRIu16 ", align=%" PRIu16 "",
+ format->wFormatTag, format->cbSize, format->nSamplesPerSec, format->wBitsPerSample,
+ format->nChannels, format->nBlockAlign);
+ WINPR_ASSERT(device);
+ WINPR_ASSERT(format);
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ if (format->cbSize == 0 && format->nSamplesPerSec <= 48000 &&
+ (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
+ (format->nChannels == 1 || format->nChannels == 2))
+ {
+ return TRUE;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+static UINT32 rdpsnd_opensles_get_volume(rdpsndDevicePlugin* device)
+{
+ rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
+ DEBUG_SND("opensles=%p", (void*)opensles);
+ WINPR_ASSERT(opensles);
+
+ if (opensles->stream)
+ {
+ const int max = android_GetOutputVolumeMax(opensles->stream);
+ const int rc = android_GetOutputVolume(opensles->stream);
+
+ if (android_GetOutputMute(opensles->stream))
+ opensles->volume = 0;
+ else
+ {
+ const unsigned short vol = rdpsnd_opensles_millibel_to_volume(rc, max);
+ opensles->volume = (vol << 16) | (vol & 0xFFFF);
+ }
+ }
+
+ return opensles->volume;
+}
+
+static BOOL rdpsnd_opensles_set_volume(rdpsndDevicePlugin* device, UINT32 value)
+{
+ rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
+ DEBUG_SND("opensles=%p, value=%" PRIu32 "", (void*)opensles, value);
+ WINPR_ASSERT(opensles);
+ opensles->volume = value;
+
+ if (opensles->stream)
+ {
+ if (0 == opensles->volume)
+ return android_SetOutputMute(opensles->stream, true);
+ else
+ {
+ const int max = android_GetOutputVolumeMax(opensles->stream);
+ const int vol = rdpsnd_opensles_volume_to_millibel(value & 0xFFFF, max);
+
+ if (!android_SetOutputMute(opensles->stream, false))
+ return FALSE;
+
+ if (!android_SetOutputVolume(opensles->stream, vol))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static UINT rdpsnd_opensles_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
+{
+ union
+ {
+ const BYTE* b;
+ const short* s;
+ } src;
+ int ret;
+ rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
+ DEBUG_SND("opensles=%p, data=%p, size=%d", (void*)opensles, (void*)data, size);
+
+ if (!rdpsnd_opensles_check_handle(opensles))
+ return 0;
+
+ src.b = data;
+ DEBUG_SND("size=%d, src=%p", size, (void*)src.b);
+ WINPR_ASSERT(0 == size % 2);
+ WINPR_ASSERT(size > 0);
+ WINPR_ASSERT(src.b);
+ ret = android_AudioOut(opensles->stream, src.s, size / 2);
+
+ if (ret < 0)
+ WLog_ERR(TAG, "android_AudioOut failed (%d)", ret);
+
+ return 10; /* TODO: Get real latencry in [ms] */
+}
+
+static void rdpsnd_opensles_start(rdpsndDevicePlugin* device)
+{
+ rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
+ rdpsnd_opensles_check_handle(opensles);
+ DEBUG_SND("opensles=%p", (void*)opensles);
+}
+
+static int rdpsnd_opensles_parse_addin_args(rdpsndDevicePlugin* device, ADDIN_ARGV* args)
+{
+ int status;
+ DWORD flags;
+ const COMMAND_LINE_ARGUMENT_A* arg;
+ rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
+ COMMAND_LINE_ARGUMENT_A rdpsnd_opensles_args[] = {
+ { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", NULL, NULL, -1, NULL, "device" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
+ };
+
+ WINPR_ASSERT(opensles);
+ WINPR_ASSERT(args);
+ DEBUG_SND("opensles=%p, args=%p", (void*)opensles, (void*)args);
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_opensles_args, flags,
+ opensles, NULL, NULL);
+
+ if (status < 0)
+ return status;
+
+ arg = rdpsnd_opensles_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
+ {
+ opensles->device_name = _strdup(arg->Value);
+
+ if (!opensles->device_name)
+ return ERROR_OUTOFMEMORY;
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT opensles_freerdp_rdpsnd_client_subsystem_entry(
+ PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ ADDIN_ARGV* args;
+ rdpsndopenslesPlugin* opensles;
+ UINT error;
+ DEBUG_SND("pEntryPoints=%p", (void*)pEntryPoints);
+ opensles = (rdpsndopenslesPlugin*)calloc(1, sizeof(rdpsndopenslesPlugin));
+
+ if (!opensles)
+ return CHANNEL_RC_NO_MEMORY;
+
+ opensles->device.Open = rdpsnd_opensles_open;
+ opensles->device.FormatSupported = rdpsnd_opensles_format_supported;
+ opensles->device.GetVolume = rdpsnd_opensles_get_volume;
+ opensles->device.SetVolume = rdpsnd_opensles_set_volume;
+ opensles->device.Start = rdpsnd_opensles_start;
+ opensles->device.Play = rdpsnd_opensles_play;
+ opensles->device.Close = rdpsnd_opensles_close;
+ opensles->device.Free = rdpsnd_opensles_free;
+ args = pEntryPoints->args;
+ rdpsnd_opensles_parse_addin_args((rdpsndDevicePlugin*)opensles, args);
+
+ if (!opensles->device_name)
+ {
+ opensles->device_name = _strdup("default");
+
+ if (!opensles->device_name)
+ {
+ error = CHANNEL_RC_NO_MEMORY;
+ goto outstrdup;
+ }
+ }
+
+ opensles->rate = 44100;
+ opensles->channels = 2;
+ opensles->format = WAVE_FORMAT_ADPCM;
+ pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)opensles);
+ DEBUG_SND("success");
+ return CHANNEL_RC_OK;
+outstrdup:
+ free(opensles);
+ return error;
+}
diff --git a/channels/rdpsnd/client/oss/CMakeLists.txt b/channels/rdpsnd/client/oss/CMakeLists.txt
new file mode 100644
index 0000000..83bd59f
--- /dev/null
+++ b/channels/rdpsnd/client/oss/CMakeLists.txt
@@ -0,0 +1,35 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("rdpsnd" "oss" "")
+
+find_package(OSS REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ rdpsnd_oss.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${OSS_LIBRARIES}
+)
+
+include_directories(..)
+include_directories(${OSS_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/rdpsnd/client/oss/rdpsnd_oss.c b/channels/rdpsnd/client/oss/rdpsnd_oss.c
new file mode 100644
index 0000000..54869ab
--- /dev/null
+++ b/channels/rdpsnd/client/oss/rdpsnd_oss.c
@@ -0,0 +1,478 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Output Virtual Channel
+ *
+ * Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/string.h>
+#include <winpr/cmdline.h>
+#include <winpr/sysinfo.h>
+#include <winpr/collections.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <unistd.h>
+#if defined(__OpenBSD__)
+#include <soundcard.h>
+#else
+#include <sys/soundcard.h>
+#endif
+#include <sys/ioctl.h>
+
+#include <freerdp/types.h>
+#include <freerdp/settings.h>
+#include <freerdp/channels/log.h>
+
+#include "rdpsnd_main.h"
+
+typedef struct
+{
+ rdpsndDevicePlugin device;
+
+ int pcm_handle;
+ int mixer_handle;
+ int dev_unit;
+
+ int supported_formats;
+
+ UINT32 latency;
+ AUDIO_FORMAT format;
+} rdpsndOssPlugin;
+
+#define OSS_LOG_ERR(_text, _error) \
+ do \
+ { \
+ if (_error != 0) \
+ { \
+ char ebuffer[256] = { 0 }; \
+ WLog_ERR(TAG, "%s: %i - %s", _text, _error, \
+ winpr_strerror(_error, ebuffer, sizeof(ebuffer))); \
+ } \
+ } while (0)
+
+static int rdpsnd_oss_get_format(const AUDIO_FORMAT* format)
+{
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ switch (format->wBitsPerSample)
+ {
+ case 8:
+ return AFMT_S8;
+
+ case 16:
+ return AFMT_S16_LE;
+ }
+
+ break;
+
+ case WAVE_FORMAT_ALAW:
+ return AFMT_A_LAW;
+
+ case WAVE_FORMAT_MULAW:
+ return AFMT_MU_LAW;
+ }
+
+ return 0;
+}
+
+static BOOL rdpsnd_oss_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
+{
+ int req_fmt = 0;
+ rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
+
+ if (device == NULL || format == NULL)
+ return FALSE;
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ if (format->cbSize != 0 || format->nSamplesPerSec > 48000 ||
+ (format->wBitsPerSample != 8 && format->wBitsPerSample != 16) ||
+ (format->nChannels != 1 && format->nChannels != 2))
+ return FALSE;
+
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ req_fmt = rdpsnd_oss_get_format(format);
+
+ /* Check really supported formats by dev. */
+ if (oss->pcm_handle != -1)
+ {
+ if ((req_fmt & oss->supported_formats) == 0)
+ return FALSE;
+ }
+ else
+ {
+ if (req_fmt == 0)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL rdpsnd_oss_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
+ UINT32 latency)
+{
+ int tmp = 0;
+ rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
+
+ if (device == NULL || oss->pcm_handle == -1 || format == NULL)
+ return FALSE;
+
+ oss->latency = latency;
+ CopyMemory(&(oss->format), format, sizeof(AUDIO_FORMAT));
+ tmp = rdpsnd_oss_get_format(format);
+
+ if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1)
+ {
+ OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed", errno);
+ return FALSE;
+ }
+
+ tmp = format->nChannels;
+
+ if (ioctl(oss->pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1)
+ {
+ OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno);
+ return FALSE;
+ }
+
+ tmp = format->nSamplesPerSec;
+
+ if (ioctl(oss->pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1)
+ {
+ OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno);
+ return FALSE;
+ }
+
+ tmp = format->nBlockAlign;
+
+ if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
+ {
+ OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed", errno);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void rdpsnd_oss_open_mixer(rdpsndOssPlugin* oss)
+{
+ int devmask = 0;
+ char mixer_name[PATH_MAX] = "/dev/mixer";
+
+ if (oss->mixer_handle != -1)
+ return;
+
+ if (oss->dev_unit != -1)
+ sprintf_s(mixer_name, PATH_MAX - 1, "/dev/mixer%i", oss->dev_unit);
+
+ if ((oss->mixer_handle = open(mixer_name, O_RDWR)) < 0)
+ {
+ OSS_LOG_ERR("mixer open failed", errno);
+ oss->mixer_handle = -1;
+ return;
+ }
+
+ if (ioctl(oss->mixer_handle, SOUND_MIXER_READ_DEVMASK, &devmask) == -1)
+ {
+ OSS_LOG_ERR("SOUND_MIXER_READ_DEVMASK failed", errno);
+ close(oss->mixer_handle);
+ oss->mixer_handle = -1;
+ return;
+ }
+}
+
+static BOOL rdpsnd_oss_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency)
+{
+ char dev_name[PATH_MAX] = "/dev/dsp";
+ rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
+
+ if (device == NULL || oss->pcm_handle != -1)
+ return TRUE;
+
+ if (oss->dev_unit != -1)
+ sprintf_s(dev_name, PATH_MAX - 1, "/dev/dsp%i", oss->dev_unit);
+
+ WLog_INFO(TAG, "open: %s", dev_name);
+
+ if ((oss->pcm_handle = open(dev_name, O_WRONLY)) < 0)
+ {
+ OSS_LOG_ERR("sound dev open failed", errno);
+ oss->pcm_handle = -1;
+ return FALSE;
+ }
+
+#if 0 /* FreeBSD OSS implementation at this moment (2015.03) does not set PCM_CAP_OUTPUT flag. */
+ int mask = 0;
+
+ if (ioctl(oss->pcm_handle, SNDCTL_DSP_GETCAPS, &mask) == -1)
+ {
+ OSS_LOG_ERR("SNDCTL_DSP_GETCAPS failed, try ignory", errno);
+ }
+ else if ((mask & PCM_CAP_OUTPUT) == 0)
+ {
+ OSS_LOG_ERR("Device does not supports playback", EOPNOTSUPP);
+ close(oss->pcm_handle);
+ oss->pcm_handle = -1;
+ return;
+ }
+
+#endif
+
+ if (ioctl(oss->pcm_handle, SNDCTL_DSP_GETFMTS, &oss->supported_formats) == -1)
+ {
+ OSS_LOG_ERR("SNDCTL_DSP_GETFMTS failed", errno);
+ close(oss->pcm_handle);
+ oss->pcm_handle = -1;
+ return FALSE;
+ }
+
+ rdpsnd_oss_set_format(device, format, latency);
+ rdpsnd_oss_open_mixer(oss);
+ return TRUE;
+}
+
+static void rdpsnd_oss_close(rdpsndDevicePlugin* device)
+{
+ rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
+
+ if (device == NULL)
+ return;
+
+ if (oss->pcm_handle != -1)
+ {
+ WLog_INFO(TAG, "close: dsp");
+ close(oss->pcm_handle);
+ oss->pcm_handle = -1;
+ }
+
+ if (oss->mixer_handle != -1)
+ {
+ WLog_INFO(TAG, "close: mixer");
+ close(oss->mixer_handle);
+ oss->mixer_handle = -1;
+ }
+}
+
+static void rdpsnd_oss_free(rdpsndDevicePlugin* device)
+{
+ rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
+
+ if (device == NULL)
+ return;
+
+ rdpsnd_oss_close(device);
+ free(oss);
+}
+
+static UINT32 rdpsnd_oss_get_volume(rdpsndDevicePlugin* device)
+{
+ int vol = 0;
+ UINT32 dwVolume = 0;
+ UINT16 dwVolumeLeft = 0;
+ UINT16 dwVolumeRight = 0;
+ rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
+ /* On error return 50% volume. */
+ dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */
+ dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */
+ dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight);
+
+ if (device == NULL || oss->mixer_handle == -1)
+ return dwVolume;
+
+ if (ioctl(oss->mixer_handle, MIXER_READ(SOUND_MIXER_VOLUME), &vol) == -1)
+ {
+ OSS_LOG_ERR("MIXER_READ", errno);
+ return dwVolume;
+ }
+
+ dwVolumeLeft = (((vol & 0x7f) * 0xFFFF) / 100);
+ dwVolumeRight = ((((vol >> 8) & 0x7f) * 0xFFFF) / 100);
+ dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight);
+ return dwVolume;
+}
+
+static BOOL rdpsnd_oss_set_volume(rdpsndDevicePlugin* device, UINT32 value)
+{
+ int left = 0;
+ int right = 0;
+ rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
+
+ if (device == NULL || oss->mixer_handle == -1)
+ return FALSE;
+
+ left = (((value & 0xFFFF) * 100) / 0xFFFF);
+ right = ((((value >> 16) & 0xFFFF) * 100) / 0xFFFF);
+
+ if (left < 0)
+ left = 0;
+ else if (left > 100)
+ left = 100;
+
+ if (right < 0)
+ right = 0;
+ else if (right > 100)
+ right = 100;
+
+ left |= (right << 8);
+
+ if (ioctl(oss->mixer_handle, MIXER_WRITE(SOUND_MIXER_VOLUME), &left) == -1)
+ {
+ OSS_LOG_ERR("WRITE_MIXER", errno);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static UINT rdpsnd_oss_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
+{
+ rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
+
+ if (device == NULL || oss->mixer_handle == -1)
+ return 0;
+
+ while (size > 0)
+ {
+ ssize_t status = write(oss->pcm_handle, data, size);
+
+ if (status < 0)
+ {
+ OSS_LOG_ERR("write fail", errno);
+ rdpsnd_oss_close(device);
+ rdpsnd_oss_open(device, NULL, oss->latency);
+ break;
+ }
+
+ data += status;
+
+ if ((size_t)status <= size)
+ size -= (size_t)status;
+ else
+ size = 0;
+ }
+
+ return 10; /* TODO: Get real latency in [ms] */
+}
+
+static int rdpsnd_oss_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
+{
+ int status = 0;
+ char* str_num = NULL;
+ char* eptr = NULL;
+ DWORD flags = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
+ COMMAND_LINE_ARGUMENT_A rdpsnd_oss_args[] = { { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
+ NULL, NULL, -1, NULL, "device" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status =
+ CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_oss_args, flags, oss, NULL, NULL);
+
+ if (status < 0)
+ return status;
+
+ arg = rdpsnd_oss_args;
+ errno = 0;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
+ {
+ str_num = _strdup(arg->Value);
+
+ if (!str_num)
+ return ERROR_OUTOFMEMORY;
+
+ {
+ long val = strtol(str_num, &eptr, 10);
+
+ if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
+ {
+ free(str_num);
+ return CHANNEL_RC_NULL_DATA;
+ }
+
+ oss->dev_unit = val;
+ }
+
+ if (oss->dev_unit < 0 || *eptr != '\0')
+ oss->dev_unit = -1;
+
+ free(str_num);
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT oss_freerdp_rdpsnd_client_subsystem_entry(
+ PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ const ADDIN_ARGV* args = NULL;
+ rdpsndOssPlugin* oss = (rdpsndOssPlugin*)calloc(1, sizeof(rdpsndOssPlugin));
+
+ if (!oss)
+ return CHANNEL_RC_NO_MEMORY;
+
+ oss->device.Open = rdpsnd_oss_open;
+ oss->device.FormatSupported = rdpsnd_oss_format_supported;
+ oss->device.GetVolume = rdpsnd_oss_get_volume;
+ oss->device.SetVolume = rdpsnd_oss_set_volume;
+ oss->device.Play = rdpsnd_oss_play;
+ oss->device.Close = rdpsnd_oss_close;
+ oss->device.Free = rdpsnd_oss_free;
+ oss->pcm_handle = -1;
+ oss->mixer_handle = -1;
+ oss->dev_unit = -1;
+ args = pEntryPoints->args;
+ if (rdpsnd_oss_parse_addin_args(&oss->device, args) < 0)
+ {
+ free(oss);
+ return ERROR_INVALID_PARAMETER;
+ }
+ pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)oss);
+ return CHANNEL_RC_OK;
+}
diff --git a/channels/rdpsnd/client/pulse/CMakeLists.txt b/channels/rdpsnd/client/pulse/CMakeLists.txt
new file mode 100644
index 0000000..d42576c
--- /dev/null
+++ b/channels/rdpsnd/client/pulse/CMakeLists.txt
@@ -0,0 +1,36 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("rdpsnd" "pulse" "")
+
+find_package(PulseAudio REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ rdpsnd_pulse.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${PULSEAUDIO_LIBRARY}
+ ${PULSEAUDIO_MAINLOOP_LIBRARY}
+)
+
+include_directories(..)
+include_directories(${PULSEAUDIO_INCLUDE_DIR})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/rdpsnd/client/pulse/rdpsnd_pulse.c b/channels/rdpsnd/client/pulse/rdpsnd_pulse.c
new file mode 100644
index 0000000..59b2076
--- /dev/null
+++ b/channels/rdpsnd/client/pulse/rdpsnd_pulse.c
@@ -0,0 +1,768 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Output Virtual Channel
+ *
+ * Copyright 2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <errno.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/stream.h>
+#include <winpr/cmdline.h>
+
+#include <pulse/pulseaudio.h>
+
+#include <freerdp/types.h>
+#include <freerdp/codec/dsp.h>
+
+#include "rdpsnd_main.h"
+
+typedef struct
+{
+ rdpsndDevicePlugin device;
+
+ char* device_name;
+ pa_threaded_mainloop* mainloop;
+ pa_context* context;
+ pa_sample_spec sample_spec;
+ pa_stream* stream;
+ UINT32 latency;
+ UINT32 volume;
+ time_t reconnect_delay_seconds;
+ time_t reconnect_time;
+} rdpsndPulsePlugin;
+
+static BOOL rdpsnd_check_pulse(rdpsndPulsePlugin* pulse, BOOL haveStream)
+{
+ BOOL rc = TRUE;
+ WINPR_ASSERT(pulse);
+
+ if (!pulse->context)
+ {
+ WLog_WARN(TAG, "pulse->context=%p", pulse->context);
+ rc = FALSE;
+ }
+
+ if (haveStream)
+ {
+ if (!pulse->stream)
+ {
+ WLog_WARN(TAG, "pulse->stream=%p", pulse->stream);
+ rc = FALSE;
+ }
+ }
+
+ if (!pulse->mainloop)
+ {
+ WLog_WARN(TAG, "pulse->mainloop=%p", pulse->mainloop);
+ rc = FALSE;
+ }
+
+ return rc;
+}
+
+static BOOL rdpsnd_pulse_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format);
+
+static void rdpsnd_pulse_get_sink_info(pa_context* c, const pa_sink_info* i, int eol,
+ void* userdata)
+{
+ UINT16 dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */
+ UINT16 dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */
+ rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
+
+ WINPR_ASSERT(c);
+ if (!rdpsnd_check_pulse(pulse, FALSE) || !i)
+ return;
+
+ for (uint8_t x = 0; x < i->volume.channels; x++)
+ {
+ pa_volume_t volume = i->volume.values[x];
+
+ if (volume >= PA_VOLUME_NORM)
+ volume = PA_VOLUME_NORM - 1;
+
+ switch (x)
+ {
+ case 0:
+ dwVolumeLeft = (UINT16)volume;
+ break;
+
+ case 1:
+ dwVolumeRight = (UINT16)volume;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ pulse->volume = ((UINT32)dwVolumeLeft << 16U) | dwVolumeRight;
+}
+
+static void rdpsnd_pulse_context_state_callback(pa_context* context, void* userdata)
+{
+ rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(pulse);
+
+ pa_context_state_t state = pa_context_get_state(context);
+
+ switch (state)
+ {
+ case PA_CONTEXT_READY:
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+ break;
+
+ case PA_CONTEXT_FAILED:
+ // Destroy context now, create new one for next connection attempt
+ pa_context_unref(pulse->context);
+ pulse->context = NULL;
+ if (pulse->reconnect_delay_seconds >= 0)
+ pulse->reconnect_time = time(NULL) + pulse->reconnect_delay_seconds;
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+ break;
+
+ case PA_CONTEXT_TERMINATED:
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static BOOL rdpsnd_pulse_connect(rdpsndDevicePlugin* device)
+{
+ BOOL rc = 0;
+ pa_operation* o = NULL;
+ pa_context_state_t state = PA_CONTEXT_FAILED;
+ rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
+
+ if (!rdpsnd_check_pulse(pulse, FALSE))
+ return FALSE;
+
+ pa_threaded_mainloop_lock(pulse->mainloop);
+
+ if (pa_context_connect(pulse->context, NULL, 0, NULL) < 0)
+ {
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ return FALSE;
+ }
+
+ for (;;)
+ {
+ state = pa_context_get_state(pulse->context);
+
+ if (state == PA_CONTEXT_READY)
+ break;
+
+ if (!PA_CONTEXT_IS_GOOD(state))
+ {
+ break;
+ }
+
+ pa_threaded_mainloop_wait(pulse->mainloop);
+ }
+
+ o = pa_context_get_sink_info_by_index(pulse->context, 0, rdpsnd_pulse_get_sink_info, pulse);
+
+ if (o)
+ pa_operation_unref(o);
+
+ if (state == PA_CONTEXT_READY)
+ {
+ rc = TRUE;
+ }
+ else
+ {
+ pa_context_disconnect(pulse->context);
+ rc = FALSE;
+ }
+
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ return rc;
+}
+
+static void rdpsnd_pulse_stream_success_callback(pa_stream* stream, int success, void* userdata)
+{
+ rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
+
+ if (!rdpsnd_check_pulse(pulse, TRUE))
+ return;
+
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+}
+
+static void rdpsnd_pulse_wait_for_operation(rdpsndPulsePlugin* pulse, pa_operation* operation)
+{
+ if (!rdpsnd_check_pulse(pulse, TRUE))
+ return;
+
+ if (!operation)
+ return;
+
+ while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
+ {
+ pa_threaded_mainloop_wait(pulse->mainloop);
+ }
+
+ pa_operation_unref(operation);
+}
+
+static void rdpsnd_pulse_stream_state_callback(pa_stream* stream, void* userdata)
+{
+ rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
+
+ WINPR_ASSERT(stream);
+ if (!rdpsnd_check_pulse(pulse, TRUE))
+ return;
+
+ pa_stream_state_t state = pa_stream_get_state(stream);
+
+ switch (state)
+ {
+ case PA_STREAM_READY:
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+ break;
+
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ // Stream object is about to be destroyed, clean up our pointer
+ pulse->stream = NULL;
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void rdpsnd_pulse_stream_request_callback(pa_stream* stream, size_t length, void* userdata)
+{
+ rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
+
+ WINPR_ASSERT(stream);
+ if (!rdpsnd_check_pulse(pulse, TRUE))
+ return;
+
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+}
+
+static void rdpsnd_pulse_close(rdpsndDevicePlugin* device)
+{
+ rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
+
+ WINPR_ASSERT(pulse);
+
+ if (!rdpsnd_check_pulse(pulse, FALSE))
+ return;
+
+ pa_threaded_mainloop_lock(pulse->mainloop);
+ if (pulse->stream)
+ {
+ rdpsnd_pulse_wait_for_operation(
+ pulse, pa_stream_drain(pulse->stream, rdpsnd_pulse_stream_success_callback, pulse));
+ pa_stream_disconnect(pulse->stream);
+ pa_stream_unref(pulse->stream);
+ pulse->stream = NULL;
+ }
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+}
+
+static BOOL rdpsnd_pulse_set_format_spec(rdpsndPulsePlugin* pulse, const AUDIO_FORMAT* format)
+{
+ pa_sample_spec sample_spec = { 0 };
+
+ WINPR_ASSERT(format);
+
+ if (!rdpsnd_check_pulse(pulse, FALSE))
+ return FALSE;
+
+ if (!rdpsnd_pulse_format_supported(&pulse->device, format))
+ return FALSE;
+
+ sample_spec.rate = format->nSamplesPerSec;
+ sample_spec.channels = format->nChannels;
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ switch (format->wBitsPerSample)
+ {
+ case 8:
+ sample_spec.format = PA_SAMPLE_U8;
+ break;
+
+ case 16:
+ sample_spec.format = PA_SAMPLE_S16LE;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ break;
+
+ case WAVE_FORMAT_ALAW:
+ sample_spec.format = PA_SAMPLE_ALAW;
+ break;
+
+ case WAVE_FORMAT_MULAW:
+ sample_spec.format = PA_SAMPLE_ULAW;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ pulse->sample_spec = sample_spec;
+ return TRUE;
+}
+
+static BOOL rdpsnd_pulse_context_connect(rdpsndDevicePlugin* device)
+{
+ rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
+
+ pulse->context = pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), "freerdp");
+
+ if (!pulse->context)
+ return FALSE;
+
+ pa_context_set_state_callback(pulse->context, rdpsnd_pulse_context_state_callback, pulse);
+
+ if (!rdpsnd_pulse_connect((rdpsndDevicePlugin*)pulse))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL rdpsnd_pulse_open_stream(rdpsndDevicePlugin* device)
+{
+ pa_stream_state_t state = PA_STREAM_FAILED;
+ pa_stream_flags_t flags = PA_STREAM_NOFLAGS;
+ pa_buffer_attr buffer_attr = { 0 };
+ char ss[PA_SAMPLE_SPEC_SNPRINT_MAX] = { 0 };
+ rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
+
+ if (pa_sample_spec_valid(&pulse->sample_spec) == 0)
+ {
+ pa_sample_spec_snprint(ss, sizeof(ss), &pulse->sample_spec);
+ return FALSE;
+ }
+
+ pa_threaded_mainloop_lock(pulse->mainloop);
+ if (!pulse->context)
+ {
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ if (pulse->reconnect_delay_seconds >= 0 && time(NULL) - pulse->reconnect_time >= 0)
+ rdpsnd_pulse_context_connect(device);
+ pa_threaded_mainloop_lock(pulse->mainloop);
+ }
+
+ if (!rdpsnd_check_pulse(pulse, FALSE))
+ {
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ return FALSE;
+ }
+
+ pulse->stream = pa_stream_new(pulse->context, "freerdp", &pulse->sample_spec, NULL);
+
+ if (!pulse->stream)
+ {
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ return FALSE;
+ }
+
+ /* register essential callbacks */
+ pa_stream_set_state_callback(pulse->stream, rdpsnd_pulse_stream_state_callback, pulse);
+ pa_stream_set_write_callback(pulse->stream, rdpsnd_pulse_stream_request_callback, pulse);
+ flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE;
+
+ if (pulse->latency > 0)
+ {
+ buffer_attr.maxlength = UINT32_MAX;
+ buffer_attr.tlength = pa_usec_to_bytes(pulse->latency * 1000, &pulse->sample_spec);
+ buffer_attr.prebuf = UINT32_MAX;
+ buffer_attr.minreq = UINT32_MAX;
+ buffer_attr.fragsize = UINT32_MAX;
+ flags |= PA_STREAM_ADJUST_LATENCY;
+ }
+
+ if (pa_stream_connect_playback(pulse->stream, pulse->device_name,
+ pulse->latency > 0 ? &buffer_attr : NULL, flags, NULL, NULL) < 0)
+ {
+ WLog_ERR(TAG, "error connecting playback stream");
+ pa_stream_unref(pulse->stream);
+ pulse->stream = NULL;
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ return FALSE;
+ }
+
+ for (;;)
+ {
+ state = pa_stream_get_state(pulse->stream);
+
+ if (state == PA_STREAM_READY)
+ break;
+
+ if (!PA_STREAM_IS_GOOD(state))
+ {
+ break;
+ }
+
+ pa_threaded_mainloop_wait(pulse->mainloop);
+ }
+
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+
+ if (state == PA_STREAM_READY)
+ return TRUE;
+
+ rdpsnd_pulse_close(device);
+ return FALSE;
+}
+
+static BOOL rdpsnd_pulse_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
+ UINT32 latency)
+{
+ rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
+
+ WINPR_ASSERT(format);
+
+ if (!rdpsnd_check_pulse(pulse, FALSE))
+ return TRUE;
+
+ if (!rdpsnd_pulse_set_format_spec(pulse, format))
+ return FALSE;
+
+ pulse->latency = latency;
+
+ return rdpsnd_pulse_open_stream(device);
+}
+
+static void rdpsnd_pulse_free(rdpsndDevicePlugin* device)
+{
+ rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
+
+ if (!pulse)
+ return;
+
+ rdpsnd_pulse_close(device);
+
+ if (pulse->mainloop)
+ pa_threaded_mainloop_stop(pulse->mainloop);
+
+ if (pulse->context)
+ {
+ pa_context_disconnect(pulse->context);
+ pa_context_unref(pulse->context);
+ pulse->context = NULL;
+ }
+
+ if (pulse->mainloop)
+ {
+ pa_threaded_mainloop_free(pulse->mainloop);
+ pulse->mainloop = NULL;
+ }
+
+ free(pulse->device_name);
+ free(pulse);
+}
+
+static BOOL rdpsnd_pulse_default_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* desired,
+ AUDIO_FORMAT* defaultFormat)
+{
+ rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
+
+ if (!pulse || !defaultFormat)
+ return FALSE;
+
+ *defaultFormat = *desired;
+ defaultFormat->data = NULL;
+ defaultFormat->cbSize = 0;
+ defaultFormat->wFormatTag = WAVE_FORMAT_PCM;
+ if ((defaultFormat->nChannels < 1) || (defaultFormat->nChannels > PA_CHANNELS_MAX))
+ defaultFormat->nChannels = 2;
+ if ((defaultFormat->nSamplesPerSec < 1) || (defaultFormat->nSamplesPerSec > PA_RATE_MAX))
+ defaultFormat->nSamplesPerSec = 44100;
+ if ((defaultFormat->wBitsPerSample != 8) && (defaultFormat->wBitsPerSample != 16))
+ defaultFormat->wBitsPerSample = 16;
+
+ defaultFormat->nBlockAlign = defaultFormat->nChannels * defaultFormat->wBitsPerSample / 8;
+ defaultFormat->nAvgBytesPerSec = defaultFormat->nBlockAlign * defaultFormat->nSamplesPerSec;
+ return TRUE;
+}
+
+BOOL rdpsnd_pulse_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
+{
+ WINPR_ASSERT(device);
+ WINPR_ASSERT(format);
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ if (format->cbSize == 0 && (format->nSamplesPerSec <= PA_RATE_MAX) &&
+ (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
+ (format->nChannels >= 1 && format->nChannels <= PA_CHANNELS_MAX))
+ {
+ return TRUE;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+static UINT32 rdpsnd_pulse_get_volume(rdpsndDevicePlugin* device)
+{
+ pa_operation* o = NULL;
+ rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
+
+ if (!rdpsnd_check_pulse(pulse, FALSE))
+ return 0;
+
+ pa_threaded_mainloop_lock(pulse->mainloop);
+ o = pa_context_get_sink_info_by_index(pulse->context, 0, rdpsnd_pulse_get_sink_info, pulse);
+ if (o)
+ pa_operation_unref(o);
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ return pulse->volume;
+}
+
+static void rdpsnd_set_volume_success_cb(pa_context* c, int success, void* userdata)
+{
+ rdpsndPulsePlugin* pulse = userdata;
+
+ if (!rdpsnd_check_pulse(pulse, TRUE))
+ return;
+ WINPR_ASSERT(c);
+
+ WLog_INFO(TAG, "%d", success);
+}
+
+static BOOL rdpsnd_pulse_set_volume(rdpsndDevicePlugin* device, UINT32 value)
+{
+ pa_cvolume cv = { 0 };
+ pa_volume_t left = 0;
+ pa_volume_t right = 0;
+ pa_operation* operation = NULL;
+ rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
+
+ if (!rdpsnd_check_pulse(pulse, TRUE))
+ {
+ WLog_WARN(TAG, "%s called before pulse backend was initialized");
+ return FALSE;
+ }
+
+ left = (pa_volume_t)(value & 0xFFFF);
+ right = (pa_volume_t)((value >> 16) & 0xFFFF);
+ pa_cvolume_init(&cv);
+ cv.channels = 2;
+ cv.values[0] = PA_VOLUME_MUTED + (left * (PA_VOLUME_NORM - PA_VOLUME_MUTED)) / PA_VOLUME_NORM;
+ cv.values[1] = PA_VOLUME_MUTED + (right * (PA_VOLUME_NORM - PA_VOLUME_MUTED)) / PA_VOLUME_NORM;
+ pa_threaded_mainloop_lock(pulse->mainloop);
+ operation = pa_context_set_sink_input_volume(pulse->context, pa_stream_get_index(pulse->stream),
+ &cv, rdpsnd_set_volume_success_cb, pulse);
+
+ if (operation)
+ pa_operation_unref(operation);
+
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ return TRUE;
+}
+
+static UINT rdpsnd_pulse_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
+{
+ size_t length = 0;
+ void* pa_data = NULL;
+ int status = 0;
+ pa_usec_t latency = 0;
+ int negative = 0;
+ rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
+
+ if (!data)
+ return 0;
+
+ pa_threaded_mainloop_lock(pulse->mainloop);
+
+ if (!rdpsnd_check_pulse(pulse, TRUE))
+ {
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ // Discard this playback request and just attempt to reconnect the stream
+ WLog_DBG(TAG, "reconnecting playback stream");
+ rdpsnd_pulse_open_stream(device);
+ return 0;
+ }
+
+ while (size > 0)
+ {
+ length = size;
+
+ status = pa_stream_begin_write(pulse->stream, &pa_data, &length);
+
+ if (status < 0)
+ break;
+
+ memcpy(pa_data, data, length);
+
+ status = pa_stream_write(pulse->stream, pa_data, length, NULL, 0LL, PA_SEEK_RELATIVE);
+
+ if (status < 0)
+ {
+ break;
+ }
+
+ data += length;
+ size -= length;
+ }
+
+ if (pa_stream_get_latency(pulse->stream, &latency, &negative) != 0)
+ latency = 0;
+
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ return latency / 1000;
+}
+
+static UINT rdpsnd_pulse_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
+{
+ int status = 0;
+ DWORD flags = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
+ COMMAND_LINE_ARGUMENT_A rdpsnd_pulse_args[] = {
+ { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", NULL, NULL, -1, NULL, "device" },
+ { "reconnect_delay_seconds", COMMAND_LINE_VALUE_REQUIRED, "<reconnect_delay_seconds>", NULL,
+ NULL, -1, NULL, "reconnect_delay_seconds" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
+ };
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+
+ WINPR_ASSERT(pulse);
+ WINPR_ASSERT(args);
+
+ status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_pulse_args, flags, pulse,
+ NULL, NULL);
+
+ if (status < 0)
+ return ERROR_INVALID_DATA;
+
+ arg = rdpsnd_pulse_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
+ {
+ pulse->device_name = _strdup(arg->Value);
+
+ if (!pulse->device_name)
+ return ERROR_OUTOFMEMORY;
+ }
+ CommandLineSwitchCase(arg, "reconnect_delay_seconds")
+ {
+ unsigned long val = strtoul(arg->Value, NULL, 0);
+
+ if ((errno != 0) || (val > INT32_MAX))
+ return ERROR_INVALID_DATA;
+
+ pulse->reconnect_delay_seconds = val;
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+FREERDP_ENTRY_POINT(UINT pulse_freerdp_rdpsnd_client_subsystem_entry(
+ PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ const ADDIN_ARGV* args = NULL;
+ rdpsndPulsePlugin* pulse = NULL;
+ UINT ret = 0;
+
+ WINPR_ASSERT(pEntryPoints);
+
+ pulse = (rdpsndPulsePlugin*)calloc(1, sizeof(rdpsndPulsePlugin));
+
+ if (!pulse)
+ return CHANNEL_RC_NO_MEMORY;
+
+ pulse->device.Open = rdpsnd_pulse_open;
+ pulse->device.FormatSupported = rdpsnd_pulse_format_supported;
+ pulse->device.GetVolume = rdpsnd_pulse_get_volume;
+ pulse->device.SetVolume = rdpsnd_pulse_set_volume;
+ pulse->device.Play = rdpsnd_pulse_play;
+ pulse->device.Close = rdpsnd_pulse_close;
+ pulse->device.Free = rdpsnd_pulse_free;
+ pulse->device.DefaultFormat = rdpsnd_pulse_default_format;
+ args = pEntryPoints->args;
+
+ if (args->argc > 1)
+ {
+ ret = rdpsnd_pulse_parse_addin_args(&pulse->device, args);
+
+ if (ret != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "error parsing arguments");
+ goto error;
+ }
+ }
+ pulse->reconnect_delay_seconds = 5;
+ pulse->reconnect_time = time(NULL);
+
+ ret = CHANNEL_RC_NO_MEMORY;
+ pulse->mainloop = pa_threaded_mainloop_new();
+
+ if (!pulse->mainloop)
+ goto error;
+
+ pa_threaded_mainloop_lock(pulse->mainloop);
+
+ if (pa_threaded_mainloop_start(pulse->mainloop) < 0)
+ {
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ return FALSE;
+ }
+
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+
+ if (!rdpsnd_pulse_context_connect((rdpsndDevicePlugin*)pulse))
+ goto error;
+
+ pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)pulse);
+ return CHANNEL_RC_OK;
+error:
+ rdpsnd_pulse_free((rdpsndDevicePlugin*)pulse);
+ return ret;
+}
diff --git a/channels/rdpsnd/client/rdpsnd_main.c b/channels/rdpsnd/client/rdpsnd_main.c
new file mode 100644
index 0000000..6c66d33
--- /dev/null
+++ b/channels/rdpsnd/client/rdpsnd_main.c
@@ -0,0 +1,1850 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Output Virtual Channel
+ *
+ * Copyright 2009-2011 Jay Sorg
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2012-2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#ifndef _WIN32
+#include <sys/time.h>
+#include <signal.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/wlog.h>
+#include <winpr/stream.h>
+#include <winpr/cmdline.h>
+#include <winpr/sysinfo.h>
+#include <winpr/collections.h>
+
+#include <freerdp/types.h>
+#include <freerdp/addin.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/codec/dsp.h>
+#include <freerdp/client/channels.h>
+
+#include "rdpsnd_common.h"
+#include "rdpsnd_main.h"
+
+struct rdpsnd_plugin
+{
+ IWTSPlugin iface;
+ IWTSListener* listener;
+ GENERIC_LISTENER_CALLBACK* listener_callback;
+
+ CHANNEL_DEF channelDef;
+ CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints;
+
+ wStreamPool* pool;
+ wStream* data_in;
+
+ void* InitHandle;
+ DWORD OpenHandle;
+
+ wLog* log;
+
+ BYTE cBlockNo;
+ UINT16 wQualityMode;
+ UINT16 wCurrentFormatNo;
+
+ AUDIO_FORMAT* ServerFormats;
+ UINT16 NumberOfServerFormats;
+
+ AUDIO_FORMAT* ClientFormats;
+ UINT16 NumberOfClientFormats;
+
+ BOOL attached;
+ BOOL connected;
+ BOOL dynamic;
+
+ BOOL expectingWave;
+ BYTE waveData[4];
+ UINT16 waveDataSize;
+ UINT16 wTimeStamp;
+ UINT64 wArrivalTime;
+
+ UINT32 latency;
+ BOOL isOpen;
+ AUDIO_FORMAT* fixed_format;
+
+ UINT32 startPlayTime;
+ size_t totalPlaySize;
+
+ char* subsystem;
+ char* device_name;
+
+ /* Device plugin */
+ rdpsndDevicePlugin* device;
+ rdpContext* rdpcontext;
+
+ FREERDP_DSP_CONTEXT* dsp_context;
+
+ HANDLE thread;
+ wMessageQueue* queue;
+ BOOL initialized;
+
+ UINT16 wVersion;
+ UINT32 volume;
+ BOOL applyVolume;
+
+ size_t references;
+ BOOL OnOpenCalled;
+ BOOL async;
+};
+
+static const char* rdpsnd_is_dyn_str(BOOL dynamic)
+{
+ if (dynamic)
+ return "[dynamic]";
+ return "[static]";
+}
+
+static void rdpsnd_virtual_channel_event_terminated(rdpsndPlugin* rdpsnd);
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_virtual_channel_write(rdpsndPlugin* rdpsnd, wStream* s);
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_send_quality_mode_pdu(rdpsndPlugin* rdpsnd)
+{
+ wStream* pdu = NULL;
+ WINPR_ASSERT(rdpsnd);
+ pdu = Stream_New(NULL, 8);
+
+ if (!pdu)
+ {
+ WLog_ERR(TAG, "%s Stream_New failed!", rdpsnd_is_dyn_str(rdpsnd->dynamic));
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT8(pdu, SNDC_QUALITYMODE); /* msgType */
+ Stream_Write_UINT8(pdu, 0); /* bPad */
+ Stream_Write_UINT16(pdu, 4); /* BodySize */
+ Stream_Write_UINT16(pdu, rdpsnd->wQualityMode); /* wQualityMode */
+ Stream_Write_UINT16(pdu, 0); /* Reserved */
+ WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s QualityMode: %" PRIu16 "",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic), rdpsnd->wQualityMode);
+ return rdpsnd_virtual_channel_write(rdpsnd, pdu);
+}
+
+static void rdpsnd_select_supported_audio_formats(rdpsndPlugin* rdpsnd)
+{
+ WINPR_ASSERT(rdpsnd);
+ audio_formats_free(rdpsnd->ClientFormats, rdpsnd->NumberOfClientFormats);
+ rdpsnd->NumberOfClientFormats = 0;
+ rdpsnd->ClientFormats = NULL;
+
+ if (!rdpsnd->NumberOfServerFormats)
+ return;
+
+ rdpsnd->ClientFormats = audio_formats_new(rdpsnd->NumberOfServerFormats);
+
+ if (!rdpsnd->ClientFormats || !rdpsnd->device)
+ return;
+
+ for (UINT16 index = 0; index < rdpsnd->NumberOfServerFormats; index++)
+ {
+ const AUDIO_FORMAT* serverFormat = &rdpsnd->ServerFormats[index];
+
+ if (!audio_format_compatible(rdpsnd->fixed_format, serverFormat))
+ continue;
+
+ WINPR_ASSERT(rdpsnd->device->FormatSupported);
+ if (freerdp_dsp_supports_format(serverFormat, FALSE) ||
+ rdpsnd->device->FormatSupported(rdpsnd->device, serverFormat))
+ {
+ AUDIO_FORMAT* clientFormat = &rdpsnd->ClientFormats[rdpsnd->NumberOfClientFormats++];
+ audio_format_copy(serverFormat, clientFormat);
+ }
+ }
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_send_client_audio_formats(rdpsndPlugin* rdpsnd)
+{
+ wStream* pdu = NULL;
+ UINT16 length = 0;
+ UINT32 dwVolume = 0;
+ UINT16 wNumberOfFormats = 0;
+ WINPR_ASSERT(rdpsnd);
+
+ if (!rdpsnd->device || (!rdpsnd->dynamic && (rdpsnd->OpenHandle == 0)))
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+
+ dwVolume = IFCALLRESULT(0, rdpsnd->device->GetVolume, rdpsnd->device);
+ wNumberOfFormats = rdpsnd->NumberOfClientFormats;
+ length = 4 + 20;
+
+ for (UINT16 index = 0; index < wNumberOfFormats; index++)
+ length += (18 + rdpsnd->ClientFormats[index].cbSize);
+
+ pdu = Stream_New(NULL, length);
+
+ if (!pdu)
+ {
+ WLog_ERR(TAG, "%s Stream_New failed!", rdpsnd_is_dyn_str(rdpsnd->dynamic));
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT8(pdu, SNDC_FORMATS); /* msgType */
+ Stream_Write_UINT8(pdu, 0); /* bPad */
+ Stream_Write_UINT16(pdu, length - 4); /* BodySize */
+ Stream_Write_UINT32(pdu, TSSNDCAPS_ALIVE | TSSNDCAPS_VOLUME); /* dwFlags */
+ Stream_Write_UINT32(pdu, dwVolume); /* dwVolume */
+ Stream_Write_UINT32(pdu, 0); /* dwPitch */
+ Stream_Write_UINT16(pdu, 0); /* wDGramPort */
+ Stream_Write_UINT16(pdu, wNumberOfFormats); /* wNumberOfFormats */
+ Stream_Write_UINT8(pdu, 0); /* cLastBlockConfirmed */
+ Stream_Write_UINT16(pdu, CHANNEL_VERSION_WIN_MAX); /* wVersion */
+ Stream_Write_UINT8(pdu, 0); /* bPad */
+
+ for (UINT16 index = 0; index < wNumberOfFormats; index++)
+ {
+ const AUDIO_FORMAT* clientFormat = &rdpsnd->ClientFormats[index];
+
+ if (!audio_format_write(pdu, clientFormat))
+ {
+ Stream_Free(pdu, TRUE);
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+
+ WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Client Audio Formats",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic));
+ return rdpsnd_virtual_channel_write(rdpsnd, pdu);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_recv_server_audio_formats_pdu(rdpsndPlugin* rdpsnd, wStream* s)
+{
+ UINT16 wNumberOfFormats = 0;
+ UINT ret = ERROR_BAD_LENGTH;
+
+ WINPR_ASSERT(rdpsnd);
+ audio_formats_free(rdpsnd->ServerFormats, rdpsnd->NumberOfServerFormats);
+ rdpsnd->NumberOfServerFormats = 0;
+ rdpsnd->ServerFormats = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 30))
+ return ERROR_BAD_LENGTH;
+
+ /* http://msdn.microsoft.com/en-us/library/cc240956.aspx */
+ Stream_Seek_UINT32(s); /* dwFlags */
+ Stream_Seek_UINT32(s); /* dwVolume */
+ Stream_Seek_UINT32(s); /* dwPitch */
+ Stream_Seek_UINT16(s); /* wDGramPort */
+ Stream_Read_UINT16(s, wNumberOfFormats);
+ Stream_Read_UINT8(s, rdpsnd->cBlockNo); /* cLastBlockConfirmed */
+ Stream_Read_UINT16(s, rdpsnd->wVersion); /* wVersion */
+ Stream_Seek_UINT8(s); /* bPad */
+ rdpsnd->NumberOfServerFormats = wNumberOfFormats;
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, wNumberOfFormats, 14ull))
+ return ERROR_BAD_LENGTH;
+
+ if (rdpsnd->NumberOfServerFormats > 0)
+ {
+ rdpsnd->ServerFormats = audio_formats_new(wNumberOfFormats);
+
+ if (!rdpsnd->ServerFormats)
+ return CHANNEL_RC_NO_MEMORY;
+
+ for (UINT16 index = 0; index < wNumberOfFormats; index++)
+ {
+ AUDIO_FORMAT* format = &rdpsnd->ServerFormats[index];
+
+ if (!audio_format_read(s, format))
+ goto out_fail;
+ }
+ }
+
+ WINPR_ASSERT(rdpsnd->device);
+ ret = IFCALLRESULT(CHANNEL_RC_OK, rdpsnd->device->ServerFormatAnnounce, rdpsnd->device,
+ rdpsnd->ServerFormats, rdpsnd->NumberOfServerFormats);
+
+ rdpsnd_select_supported_audio_formats(rdpsnd);
+ WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Server Audio Formats",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic));
+ ret = rdpsnd_send_client_audio_formats(rdpsnd);
+
+ if (ret == CHANNEL_RC_OK)
+ {
+ if (rdpsnd->wVersion >= CHANNEL_VERSION_WIN_7)
+ ret = rdpsnd_send_quality_mode_pdu(rdpsnd);
+ }
+
+ return ret;
+out_fail:
+ audio_formats_free(rdpsnd->ServerFormats, rdpsnd->NumberOfServerFormats);
+ rdpsnd->ServerFormats = NULL;
+ rdpsnd->NumberOfServerFormats = 0;
+ return ret;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_send_training_confirm_pdu(rdpsndPlugin* rdpsnd, UINT16 wTimeStamp,
+ UINT16 wPackSize)
+{
+ wStream* pdu = NULL;
+ WINPR_ASSERT(rdpsnd);
+ pdu = Stream_New(NULL, 8);
+
+ if (!pdu)
+ {
+ WLog_ERR(TAG, "%s Stream_New failed!", rdpsnd_is_dyn_str(rdpsnd->dynamic));
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT8(pdu, SNDC_TRAINING); /* msgType */
+ Stream_Write_UINT8(pdu, 0); /* bPad */
+ Stream_Write_UINT16(pdu, 4); /* BodySize */
+ Stream_Write_UINT16(pdu, wTimeStamp);
+ Stream_Write_UINT16(pdu, wPackSize);
+ WLog_Print(rdpsnd->log, WLOG_DEBUG,
+ "%s Training Response: wTimeStamp: %" PRIu16 " wPackSize: %" PRIu16 "",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic), wTimeStamp, wPackSize);
+ return rdpsnd_virtual_channel_write(rdpsnd, pdu);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_recv_training_pdu(rdpsndPlugin* rdpsnd, wStream* s)
+{
+ UINT16 wTimeStamp = 0;
+ UINT16 wPackSize = 0;
+ WINPR_ASSERT(rdpsnd);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_BAD_LENGTH;
+
+ Stream_Read_UINT16(s, wTimeStamp);
+ Stream_Read_UINT16(s, wPackSize);
+ WLog_Print(rdpsnd->log, WLOG_DEBUG,
+ "%s Training Request: wTimeStamp: %" PRIu16 " wPackSize: %" PRIu16 "",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic), wTimeStamp, wPackSize);
+ return rdpsnd_send_training_confirm_pdu(rdpsnd, wTimeStamp, wPackSize);
+}
+
+static BOOL rdpsnd_apply_volume(rdpsndPlugin* rdpsnd)
+{
+ WINPR_ASSERT(rdpsnd);
+
+ if (rdpsnd->isOpen && rdpsnd->applyVolume && rdpsnd->device)
+ {
+ BOOL rc = IFCALLRESULT(TRUE, rdpsnd->device->SetVolume, rdpsnd->device, rdpsnd->volume);
+ if (!rc)
+ return FALSE;
+ rdpsnd->applyVolume = FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL rdpsnd_ensure_device_is_open(rdpsndPlugin* rdpsnd, UINT32 wFormatNo,
+ const AUDIO_FORMAT* format)
+{
+ if (!rdpsnd)
+ return FALSE;
+ WINPR_ASSERT(format);
+
+ if (!rdpsnd->isOpen || (wFormatNo != rdpsnd->wCurrentFormatNo))
+ {
+ BOOL rc = 0;
+ BOOL supported = 0;
+ AUDIO_FORMAT deviceFormat = *format;
+
+ IFCALL(rdpsnd->device->Close, rdpsnd->device);
+ supported = IFCALLRESULT(FALSE, rdpsnd->device->FormatSupported, rdpsnd->device, format);
+
+ if (!supported)
+ {
+ if (!IFCALLRESULT(FALSE, rdpsnd->device->DefaultFormat, rdpsnd->device, format,
+ &deviceFormat))
+ {
+ deviceFormat.wFormatTag = WAVE_FORMAT_PCM;
+ deviceFormat.wBitsPerSample = 16;
+ deviceFormat.cbSize = 0;
+ }
+ }
+
+ WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Opening device with format %s [backend %s]",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic),
+ audio_format_get_tag_string(format->wFormatTag),
+ audio_format_get_tag_string(deviceFormat.wFormatTag));
+ rc = IFCALLRESULT(FALSE, rdpsnd->device->Open, rdpsnd->device, &deviceFormat,
+ rdpsnd->latency);
+
+ if (!rc)
+ return FALSE;
+
+ if (!supported)
+ {
+ if (!freerdp_dsp_context_reset(rdpsnd->dsp_context, format, 0u))
+ return FALSE;
+ }
+
+ rdpsnd->isOpen = TRUE;
+ rdpsnd->wCurrentFormatNo = wFormatNo;
+ rdpsnd->startPlayTime = 0;
+ rdpsnd->totalPlaySize = 0;
+ }
+
+ return rdpsnd_apply_volume(rdpsnd);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_recv_wave_info_pdu(rdpsndPlugin* rdpsnd, wStream* s, UINT16 BodySize)
+{
+ UINT16 wFormatNo = 0;
+ const AUDIO_FORMAT* format = NULL;
+ WINPR_ASSERT(rdpsnd);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return ERROR_BAD_LENGTH;
+
+ rdpsnd->wArrivalTime = GetTickCount64();
+ Stream_Read_UINT16(s, rdpsnd->wTimeStamp);
+ Stream_Read_UINT16(s, wFormatNo);
+
+ if (wFormatNo >= rdpsnd->NumberOfClientFormats)
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT8(s, rdpsnd->cBlockNo);
+ Stream_Seek(s, 3); /* bPad */
+ Stream_Read(s, rdpsnd->waveData, 4);
+ rdpsnd->waveDataSize = BodySize - 8;
+ format = &rdpsnd->ClientFormats[wFormatNo];
+ WLog_Print(rdpsnd->log, WLOG_DEBUG,
+ "%s WaveInfo: cBlockNo: %" PRIu8 " wFormatNo: %" PRIu16 " [%s]",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic), rdpsnd->cBlockNo, wFormatNo,
+ audio_format_get_tag_string(format->wFormatTag));
+
+ if (!rdpsnd_ensure_device_is_open(rdpsnd, wFormatNo, format))
+ return ERROR_INTERNAL_ERROR;
+
+ rdpsnd->expectingWave = TRUE;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_send_wave_confirm_pdu(rdpsndPlugin* rdpsnd, UINT16 wTimeStamp,
+ BYTE cConfirmedBlockNo)
+{
+ wStream* pdu = NULL;
+ WINPR_ASSERT(rdpsnd);
+ pdu = Stream_New(NULL, 8);
+
+ if (!pdu)
+ {
+ WLog_ERR(TAG, "%s Stream_New failed!", rdpsnd_is_dyn_str(rdpsnd->dynamic));
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT8(pdu, SNDC_WAVECONFIRM);
+ Stream_Write_UINT8(pdu, 0);
+ Stream_Write_UINT16(pdu, 4);
+ Stream_Write_UINT16(pdu, wTimeStamp);
+ Stream_Write_UINT8(pdu, cConfirmedBlockNo); /* cConfirmedBlockNo */
+ Stream_Write_UINT8(pdu, 0); /* bPad */
+ return rdpsnd_virtual_channel_write(rdpsnd, pdu);
+}
+
+static BOOL rdpsnd_detect_overrun(rdpsndPlugin* rdpsnd, const AUDIO_FORMAT* format, size_t size)
+{
+ UINT32 bpf = 0;
+ UINT32 now = 0;
+ UINT32 duration = 0;
+ UINT32 totalDuration = 0;
+ UINT32 remainingDuration = 0;
+ UINT32 maxDuration = 0;
+
+ if (!rdpsnd || !format)
+ return FALSE;
+
+ /* Older windows RDP servers do not limit the send buffer, which can
+ * cause quite a large amount of sound data buffered client side.
+ * If e.g. sound is paused server side the client will keep playing
+ * for a long time instead of pausing playback.
+ *
+ * To avoid this we check:
+ *
+ * 1. Is the sound sample received from a known format these servers
+ * support
+ * 2. If it is calculate the size of the client side sound buffer
+ * 3. If the buffer is too large silently drop the sample which will
+ * trigger a retransmit later on.
+ *
+ * This check must only be applied to these known formats, because
+ * with newer and other formats the sample size can not be calculated
+ * without decompressing the sample first.
+ */
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ case WAVE_FORMAT_DVI_ADPCM:
+ case WAVE_FORMAT_ADPCM:
+ case WAVE_FORMAT_ALAW:
+ case WAVE_FORMAT_MULAW:
+ break;
+ case WAVE_FORMAT_MSG723:
+ case WAVE_FORMAT_GSM610:
+ case WAVE_FORMAT_AAC_MS:
+ default:
+ return FALSE;
+ }
+
+ audio_format_print(WLog_Get(TAG), WLOG_DEBUG, format);
+ bpf = format->nChannels * format->wBitsPerSample * format->nSamplesPerSec / 8;
+ if (bpf == 0)
+ return FALSE;
+
+ duration = (UINT32)(1000 * size / bpf);
+ totalDuration = (UINT32)(1000 * rdpsnd->totalPlaySize / bpf);
+ now = GetTickCountPrecise();
+ if (rdpsnd->startPlayTime == 0)
+ {
+ rdpsnd->startPlayTime = now;
+ rdpsnd->totalPlaySize = size;
+ return FALSE;
+ }
+ else if (now - rdpsnd->startPlayTime > totalDuration + 10)
+ {
+ /* Buffer underrun */
+ WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Buffer underrun by %u ms",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic),
+ (UINT)(now - rdpsnd->startPlayTime - totalDuration));
+ rdpsnd->startPlayTime = now;
+ rdpsnd->totalPlaySize = size;
+ return FALSE;
+ }
+ else
+ {
+ /* Calculate remaining duration to be played */
+ remainingDuration = totalDuration - (now - rdpsnd->startPlayTime);
+
+ /* Maximum allow duration calculation */
+ maxDuration = duration * 2 + rdpsnd->latency;
+
+ if (remainingDuration + duration > maxDuration)
+ {
+ WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Buffer overrun pending %u ms dropping %u ms",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic), remainingDuration, duration);
+ return TRUE;
+ }
+
+ rdpsnd->totalPlaySize += size;
+ return FALSE;
+ }
+}
+
+static UINT rdpsnd_treat_wave(rdpsndPlugin* rdpsnd, wStream* s, size_t size)
+{
+ AUDIO_FORMAT* format = NULL;
+ UINT64 end = 0;
+ UINT64 diffMS = 0;
+ UINT64 ts = 0;
+ UINT latency = 0;
+ UINT error = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, size))
+ return ERROR_BAD_LENGTH;
+
+ if (rdpsnd->wCurrentFormatNo >= rdpsnd->NumberOfClientFormats)
+ return ERROR_INTERNAL_ERROR;
+
+ /*
+ * Send the first WaveConfirm PDU. The server side uses this to determine the
+ * network latency.
+ * See also [MS-RDPEA] 2.2.3.8 Wave Confirm PDU
+ */
+ error = rdpsnd_send_wave_confirm_pdu(rdpsnd, rdpsnd->wTimeStamp, rdpsnd->cBlockNo);
+ if (error)
+ return error;
+
+ const BYTE* data = Stream_ConstPointer(s);
+ format = &rdpsnd->ClientFormats[rdpsnd->wCurrentFormatNo];
+ WLog_Print(rdpsnd->log, WLOG_DEBUG,
+ "%s Wave: cBlockNo: %" PRIu8 " wTimeStamp: %" PRIu16 ", size: %" PRIdz,
+ rdpsnd_is_dyn_str(rdpsnd->dynamic), rdpsnd->cBlockNo, rdpsnd->wTimeStamp, size);
+
+ if (rdpsnd->device && rdpsnd->attached && !rdpsnd_detect_overrun(rdpsnd, format, size))
+ {
+ UINT status = CHANNEL_RC_OK;
+ wStream* pcmData = StreamPool_Take(rdpsnd->pool, 4096);
+
+ if (rdpsnd->device->FormatSupported(rdpsnd->device, format))
+ {
+ if (rdpsnd->device->PlayEx)
+ latency = rdpsnd->device->PlayEx(rdpsnd->device, format, data, size);
+ else
+ latency = IFCALLRESULT(0, rdpsnd->device->Play, rdpsnd->device, data, size);
+ }
+ else if (freerdp_dsp_decode(rdpsnd->dsp_context, format, data, size, pcmData))
+ {
+ Stream_SealLength(pcmData);
+
+ if (rdpsnd->device->PlayEx)
+ latency = rdpsnd->device->PlayEx(rdpsnd->device, format, Stream_Buffer(pcmData),
+ Stream_Length(pcmData));
+ else
+ latency = IFCALLRESULT(0, rdpsnd->device->Play, rdpsnd->device,
+ Stream_Buffer(pcmData), Stream_Length(pcmData));
+ }
+ else
+ status = ERROR_INTERNAL_ERROR;
+
+ Stream_Release(pcmData);
+
+ if (status != CHANNEL_RC_OK)
+ return status;
+ }
+
+ end = GetTickCount64();
+ diffMS = end - rdpsnd->wArrivalTime + latency;
+ ts = (rdpsnd->wTimeStamp + diffMS) % UINT16_MAX;
+
+ /*
+ * Send the second WaveConfirm PDU. With the first WaveConfirm PDU,
+ * the server side uses this second WaveConfirm PDU to determine the actual
+ * render latency.
+ */
+ return rdpsnd_send_wave_confirm_pdu(rdpsnd, (UINT16)ts, rdpsnd->cBlockNo);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_recv_wave_pdu(rdpsndPlugin* rdpsnd, wStream* s)
+{
+ rdpsnd->expectingWave = FALSE;
+
+ /**
+ * The Wave PDU is a special case: it is always sent after a Wave Info PDU,
+ * and we do not process its header. Instead, the header is pad that needs
+ * to be filled with the first four bytes of the audio sample data sent as
+ * part of the preceding Wave Info PDU.
+ */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ CopyMemory(Stream_Buffer(s), rdpsnd->waveData, 4);
+ return rdpsnd_treat_wave(rdpsnd, s, rdpsnd->waveDataSize);
+}
+
+static UINT rdpsnd_recv_wave2_pdu(rdpsndPlugin* rdpsnd, wStream* s, UINT16 BodySize)
+{
+ UINT16 wFormatNo = 0;
+ AUDIO_FORMAT* format = NULL;
+ UINT32 dwAudioTimeStamp = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return ERROR_BAD_LENGTH;
+
+ Stream_Read_UINT16(s, rdpsnd->wTimeStamp);
+ Stream_Read_UINT16(s, wFormatNo);
+ Stream_Read_UINT8(s, rdpsnd->cBlockNo);
+ Stream_Seek(s, 3); /* bPad */
+ Stream_Read_UINT32(s, dwAudioTimeStamp);
+ if (wFormatNo >= rdpsnd->NumberOfClientFormats)
+ return ERROR_INVALID_DATA;
+ format = &rdpsnd->ClientFormats[wFormatNo];
+ rdpsnd->waveDataSize = BodySize - 12;
+ rdpsnd->wArrivalTime = GetTickCount64();
+ WLog_Print(rdpsnd->log, WLOG_DEBUG,
+ "%s Wave2PDU: cBlockNo: %" PRIu8 " wFormatNo: %" PRIu16 " [%s] , align=%hu",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic), rdpsnd->cBlockNo, wFormatNo,
+ audio_format_get_tag_string(format->wFormatTag), format->nBlockAlign);
+
+ if (!rdpsnd_ensure_device_is_open(rdpsnd, wFormatNo, format))
+ return ERROR_INTERNAL_ERROR;
+
+ return rdpsnd_treat_wave(rdpsnd, s, rdpsnd->waveDataSize);
+}
+
+static void rdpsnd_recv_close_pdu(rdpsndPlugin* rdpsnd)
+{
+ if (rdpsnd->isOpen)
+ {
+ WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Closing device",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic));
+ }
+ else
+ WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Device already closed",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic));
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_recv_volume_pdu(rdpsndPlugin* rdpsnd, wStream* s)
+{
+ BOOL rc = TRUE;
+ UINT32 dwVolume = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_BAD_LENGTH;
+
+ Stream_Read_UINT32(s, dwVolume);
+ WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Volume: 0x%08" PRIX32 "",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic), dwVolume);
+
+ rdpsnd->volume = dwVolume;
+ rdpsnd->applyVolume = TRUE;
+ rc = rdpsnd_apply_volume(rdpsnd);
+
+ if (!rc)
+ {
+ WLog_ERR(TAG, "%s error setting volume", rdpsnd_is_dyn_str(rdpsnd->dynamic));
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_recv_pdu(rdpsndPlugin* rdpsnd, wStream* s)
+{
+ BYTE msgType = 0;
+ UINT16 BodySize = 0;
+ UINT status = CHANNEL_RC_OK;
+
+ if (rdpsnd->expectingWave)
+ {
+ status = rdpsnd_recv_wave_pdu(rdpsnd, s);
+ goto out;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ {
+ status = ERROR_BAD_LENGTH;
+ goto out;
+ }
+
+ Stream_Read_UINT8(s, msgType); /* msgType */
+ Stream_Seek_UINT8(s); /* bPad */
+ Stream_Read_UINT16(s, BodySize);
+
+ switch (msgType)
+ {
+ case SNDC_FORMATS:
+ status = rdpsnd_recv_server_audio_formats_pdu(rdpsnd, s);
+ break;
+
+ case SNDC_TRAINING:
+ status = rdpsnd_recv_training_pdu(rdpsnd, s);
+ break;
+
+ case SNDC_WAVE:
+ status = rdpsnd_recv_wave_info_pdu(rdpsnd, s, BodySize);
+ break;
+
+ case SNDC_CLOSE:
+ rdpsnd_recv_close_pdu(rdpsnd);
+ break;
+
+ case SNDC_SETVOLUME:
+ status = rdpsnd_recv_volume_pdu(rdpsnd, s);
+ break;
+
+ case SNDC_WAVE2:
+ status = rdpsnd_recv_wave2_pdu(rdpsnd, s, BodySize);
+ break;
+
+ default:
+ WLog_ERR(TAG, "%s unknown msgType %" PRIu8 "", rdpsnd_is_dyn_str(rdpsnd->dynamic),
+ msgType);
+ break;
+ }
+
+out:
+ Stream_Release(s);
+ return status;
+}
+
+static void rdpsnd_register_device_plugin(rdpsndPlugin* rdpsnd, rdpsndDevicePlugin* device)
+{
+ if (rdpsnd->device)
+ {
+ WLog_ERR(TAG, "%s existing device, abort.", rdpsnd_is_dyn_str(FALSE));
+ return;
+ }
+
+ rdpsnd->device = device;
+ device->rdpsnd = rdpsnd;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_load_device_plugin(rdpsndPlugin* rdpsnd, const char* name,
+ const ADDIN_ARGV* args)
+{
+ PFREERDP_RDPSND_DEVICE_ENTRY entry = NULL;
+ FREERDP_RDPSND_DEVICE_ENTRY_POINTS entryPoints;
+ UINT error = 0;
+ DWORD flags = FREERDP_ADDIN_CHANNEL_STATIC | FREERDP_ADDIN_CHANNEL_ENTRYEX;
+ if (rdpsnd->dynamic)
+ flags = FREERDP_ADDIN_CHANNEL_DYNAMIC;
+ entry = (PFREERDP_RDPSND_DEVICE_ENTRY)freerdp_load_channel_addin_entry(RDPSND_CHANNEL_NAME,
+ name, NULL, flags);
+
+ if (!entry)
+ return ERROR_INTERNAL_ERROR;
+
+ entryPoints.rdpsnd = rdpsnd;
+ entryPoints.pRegisterRdpsndDevice = rdpsnd_register_device_plugin;
+ entryPoints.args = args;
+
+ if ((error = entry(&entryPoints)))
+ WLog_ERR(TAG, "%s %s entry returns error %" PRIu32 "", rdpsnd_is_dyn_str(rdpsnd->dynamic),
+ name, error);
+
+ WLog_INFO(TAG, "%s Loaded %s backend for rdpsnd", rdpsnd_is_dyn_str(rdpsnd->dynamic), name);
+ return error;
+}
+
+static BOOL rdpsnd_set_subsystem(rdpsndPlugin* rdpsnd, const char* subsystem)
+{
+ free(rdpsnd->subsystem);
+ rdpsnd->subsystem = _strdup(subsystem);
+ return (rdpsnd->subsystem != NULL);
+}
+
+static BOOL rdpsnd_set_device_name(rdpsndPlugin* rdpsnd, const char* device_name)
+{
+ free(rdpsnd->device_name);
+ rdpsnd->device_name = _strdup(device_name);
+ return (rdpsnd->device_name != NULL);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_process_addin_args(rdpsndPlugin* rdpsnd, const ADDIN_ARGV* args)
+{
+ int status = 0;
+ DWORD flags = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ COMMAND_LINE_ARGUMENT_A rdpsnd_args[] = {
+ { "sys", COMMAND_LINE_VALUE_REQUIRED, "<subsystem>", NULL, NULL, -1, NULL, "subsystem" },
+ { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", NULL, NULL, -1, NULL, "device" },
+ { "format", COMMAND_LINE_VALUE_REQUIRED, "<format>", NULL, NULL, -1, NULL, "format" },
+ { "rate", COMMAND_LINE_VALUE_REQUIRED, "<rate>", NULL, NULL, -1, NULL, "rate" },
+ { "channel", COMMAND_LINE_VALUE_REQUIRED, "<channel>", NULL, NULL, -1, NULL, "channel" },
+ { "latency", COMMAND_LINE_VALUE_REQUIRED, "<latency>", NULL, NULL, -1, NULL, "latency" },
+ { "quality", COMMAND_LINE_VALUE_REQUIRED, "<quality mode>", NULL, NULL, -1, NULL,
+ "quality mode" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
+ };
+ rdpsnd->wQualityMode = HIGH_QUALITY; /* default quality mode */
+
+ if (args->argc > 1)
+ {
+ flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON;
+ status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_args, flags, rdpsnd,
+ NULL, NULL);
+
+ if (status < 0)
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+
+ arg = rdpsnd_args;
+ errno = 0;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "sys")
+ {
+ if (!rdpsnd_set_subsystem(rdpsnd, arg->Value))
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "dev")
+ {
+ if (!rdpsnd_set_device_name(rdpsnd, arg->Value))
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "format")
+ {
+ unsigned long val = strtoul(arg->Value, NULL, 0);
+
+ if ((errno != 0) || (val > UINT16_MAX))
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+
+ rdpsnd->fixed_format->wFormatTag = (UINT16)val;
+ }
+ CommandLineSwitchCase(arg, "rate")
+ {
+ unsigned long val = strtoul(arg->Value, NULL, 0);
+
+ if ((errno != 0) || (val > UINT32_MAX))
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+
+ rdpsnd->fixed_format->nSamplesPerSec = val;
+ }
+ CommandLineSwitchCase(arg, "channel")
+ {
+ unsigned long val = strtoul(arg->Value, NULL, 0);
+
+ if ((errno != 0) || (val > UINT16_MAX))
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+
+ rdpsnd->fixed_format->nChannels = (UINT16)val;
+ }
+ CommandLineSwitchCase(arg, "latency")
+ {
+ unsigned long val = strtoul(arg->Value, NULL, 0);
+
+ if ((errno != 0) || (val > INT32_MAX))
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+
+ rdpsnd->latency = val;
+ }
+ CommandLineSwitchCase(arg, "quality")
+ {
+ long wQualityMode = DYNAMIC_QUALITY;
+
+ if (_stricmp(arg->Value, "dynamic") == 0)
+ wQualityMode = DYNAMIC_QUALITY;
+ else if (_stricmp(arg->Value, "medium") == 0)
+ wQualityMode = MEDIUM_QUALITY;
+ else if (_stricmp(arg->Value, "high") == 0)
+ wQualityMode = HIGH_QUALITY;
+ else
+ {
+ wQualityMode = strtol(arg->Value, NULL, 0);
+
+ if (errno != 0)
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+ }
+
+ if ((wQualityMode < 0) || (wQualityMode > 2))
+ wQualityMode = DYNAMIC_QUALITY;
+
+ rdpsnd->wQualityMode = (UINT16)wQualityMode;
+ }
+ CommandLineSwitchDefault(arg)
+ {
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_process_connect(rdpsndPlugin* rdpsnd)
+{
+ const struct
+ {
+ const char* subsystem;
+ const char* device;
+ } backends[] = {
+#if defined(WITH_IOSAUDIO)
+ { "ios", "" },
+#endif
+#if defined(WITH_OPENSLES)
+ { "opensles", "" },
+#endif
+#if defined(WITH_PULSE)
+ { "pulse", "" },
+#endif
+#if defined(WITH_ALSA)
+ { "alsa", "default" },
+#endif
+#if defined(WITH_OSS)
+ { "oss", "" },
+#endif
+#if defined(WITH_MACAUDIO)
+ { "mac", "default" },
+#endif
+#if defined(WITH_WINMM)
+ { "winmm", "" },
+#endif
+#if defined(WITH_SNDIO)
+ { "sndio", "" },
+#endif
+ { "fake", "" }
+ };
+ const ADDIN_ARGV* args = NULL;
+ UINT status = ERROR_INTERNAL_ERROR;
+ WINPR_ASSERT(rdpsnd);
+ rdpsnd->latency = 0;
+ args = (const ADDIN_ARGV*)rdpsnd->channelEntryPoints.pExtendedData;
+
+ if (args)
+ {
+ status = rdpsnd_process_addin_args(rdpsnd, args);
+
+ if (status != CHANNEL_RC_OK)
+ return status;
+ }
+
+ if (rdpsnd->subsystem)
+ {
+ if ((status = rdpsnd_load_device_plugin(rdpsnd, rdpsnd->subsystem, args)))
+ {
+ WLog_ERR(TAG,
+ "%s Unable to load sound playback subsystem %s because of error %" PRIu32 "",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic), rdpsnd->subsystem, status);
+ return status;
+ }
+ }
+ else
+ {
+ for (size_t x = 0; x < ARRAYSIZE(backends); x++)
+ {
+ const char* subsystem_name = backends[x].subsystem;
+ const char* device_name = backends[x].device;
+
+ if ((status = rdpsnd_load_device_plugin(rdpsnd, subsystem_name, args)))
+ WLog_ERR(TAG,
+ "%s Unable to load sound playback subsystem %s because of error %" PRIu32
+ "",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic), subsystem_name, status);
+
+ if (!rdpsnd->device)
+ continue;
+
+ if (!rdpsnd_set_subsystem(rdpsnd, subsystem_name) ||
+ !rdpsnd_set_device_name(rdpsnd, device_name))
+ return CHANNEL_RC_NO_MEMORY;
+
+ break;
+ }
+
+ if (!rdpsnd->device || status)
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpsnd_virtual_channel_write(rdpsndPlugin* rdpsnd, wStream* s)
+{
+ UINT status = CHANNEL_RC_BAD_INIT_HANDLE;
+
+ if (rdpsnd)
+ {
+ if (rdpsnd->dynamic)
+ {
+ IWTSVirtualChannel* channel = NULL;
+ if (rdpsnd->listener_callback)
+ {
+ channel = rdpsnd->listener_callback->channel_callback->channel;
+ status = channel->Write(channel, (UINT32)Stream_Length(s), Stream_Buffer(s), NULL);
+ }
+ Stream_Free(s, TRUE);
+ }
+ else
+ {
+ status = rdpsnd->channelEntryPoints.pVirtualChannelWriteEx(
+ rdpsnd->InitHandle, rdpsnd->OpenHandle, Stream_Buffer(s),
+ (UINT32)Stream_GetPosition(s), s);
+
+ if (status != CHANNEL_RC_OK)
+ {
+ Stream_Free(s, TRUE);
+ WLog_ERR(TAG, "%s pVirtualChannelWriteEx failed with %s [%08" PRIX32 "]",
+ rdpsnd_is_dyn_str(FALSE), WTSErrorToString(status), status);
+ }
+ }
+ }
+
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_virtual_channel_event_data_received(rdpsndPlugin* plugin, void* pData,
+ UINT32 dataLength, UINT32 totalLength,
+ UINT32 dataFlags)
+{
+ if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME))
+ return CHANNEL_RC_OK;
+
+ if (dataFlags & CHANNEL_FLAG_FIRST)
+ {
+ if (!plugin->data_in)
+ plugin->data_in = StreamPool_Take(plugin->pool, totalLength);
+
+ Stream_SetPosition(plugin->data_in, 0);
+ }
+
+ if (!Stream_EnsureRemainingCapacity(plugin->data_in, dataLength))
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write(plugin->data_in, pData, dataLength);
+
+ if (dataFlags & CHANNEL_FLAG_LAST)
+ {
+ Stream_SealLength(plugin->data_in);
+ Stream_SetPosition(plugin->data_in, 0);
+
+ if (plugin->async)
+ {
+ if (!MessageQueue_Post(plugin->queue, NULL, 0, plugin->data_in, NULL))
+ return ERROR_INTERNAL_ERROR;
+ plugin->data_in = NULL;
+ }
+ else
+ {
+ UINT error = rdpsnd_recv_pdu(plugin, plugin->data_in);
+ plugin->data_in = NULL;
+ if (error)
+ return error;
+ }
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static VOID VCAPITYPE rdpsnd_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle,
+ UINT event, LPVOID pData,
+ UINT32 dataLength, UINT32 totalLength,
+ UINT32 dataFlags)
+{
+ UINT error = CHANNEL_RC_OK;
+ rdpsndPlugin* rdpsnd = (rdpsndPlugin*)lpUserParam;
+ WINPR_ASSERT(rdpsnd);
+ WINPR_ASSERT(!rdpsnd->dynamic);
+
+ switch (event)
+ {
+ case CHANNEL_EVENT_DATA_RECEIVED:
+ if (!rdpsnd)
+ return;
+
+ if (rdpsnd->OpenHandle != openHandle)
+ {
+ WLog_ERR(TAG, "%s error no match", rdpsnd_is_dyn_str(rdpsnd->dynamic));
+ return;
+ }
+ if ((error = rdpsnd_virtual_channel_event_data_received(rdpsnd, pData, dataLength,
+ totalLength, dataFlags)))
+ WLog_ERR(TAG,
+ "%s rdpsnd_virtual_channel_event_data_received failed with error %" PRIu32
+ "",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic), error);
+
+ break;
+
+ case CHANNEL_EVENT_WRITE_CANCELLED:
+ case CHANNEL_EVENT_WRITE_COMPLETE:
+ {
+ wStream* s = (wStream*)pData;
+ Stream_Free(s, TRUE);
+ }
+ break;
+
+ case CHANNEL_EVENT_USER:
+ break;
+ }
+
+ if (error && rdpsnd && rdpsnd->rdpcontext)
+ {
+ char buffer[8192];
+ _snprintf(buffer, sizeof(buffer),
+ "%s rdpsnd_virtual_channel_open_event_ex reported an error",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic));
+ setChannelError(rdpsnd->rdpcontext, error, buffer);
+ }
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_virtual_channel_event_connected(rdpsndPlugin* rdpsnd, LPVOID pData,
+ UINT32 dataLength)
+{
+ UINT32 status = 0;
+ DWORD opened = 0;
+ WINPR_UNUSED(pData);
+ WINPR_UNUSED(dataLength);
+
+ WINPR_ASSERT(rdpsnd);
+ WINPR_ASSERT(!rdpsnd->dynamic);
+
+ status = rdpsnd->channelEntryPoints.pVirtualChannelOpenEx(
+ rdpsnd->InitHandle, &opened, rdpsnd->channelDef.name, rdpsnd_virtual_channel_open_event_ex);
+
+ if (status != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "%s pVirtualChannelOpenEx failed with %s [%08" PRIX32 "]",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic), WTSErrorToString(status), status);
+ goto fail;
+ }
+
+ if (rdpsnd_process_connect(rdpsnd) != CHANNEL_RC_OK)
+ goto fail;
+
+ rdpsnd->OpenHandle = opened;
+ return CHANNEL_RC_OK;
+fail:
+ if (opened != 0)
+ rdpsnd->channelEntryPoints.pVirtualChannelCloseEx(rdpsnd->InitHandle, opened);
+ return CHANNEL_RC_NO_MEMORY;
+}
+
+static void cleanup_internals(rdpsndPlugin* rdpsnd)
+{
+ if (!rdpsnd)
+ return;
+
+ if (rdpsnd->pool)
+ StreamPool_Return(rdpsnd->pool, rdpsnd->data_in);
+
+ audio_formats_free(rdpsnd->ClientFormats, rdpsnd->NumberOfClientFormats);
+ audio_formats_free(rdpsnd->ServerFormats, rdpsnd->NumberOfServerFormats);
+
+ rdpsnd->NumberOfClientFormats = 0;
+ rdpsnd->ClientFormats = NULL;
+ rdpsnd->NumberOfServerFormats = 0;
+ rdpsnd->ServerFormats = NULL;
+
+ rdpsnd->data_in = NULL;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_virtual_channel_event_disconnected(rdpsndPlugin* rdpsnd)
+{
+ UINT error = 0;
+
+ WINPR_ASSERT(rdpsnd);
+ WINPR_ASSERT(!rdpsnd->dynamic);
+ if (rdpsnd->OpenHandle != 0)
+ {
+ DWORD opened = rdpsnd->OpenHandle;
+ rdpsnd->OpenHandle = 0;
+ if (rdpsnd->device)
+ IFCALL(rdpsnd->device->Close, rdpsnd->device);
+
+ error = rdpsnd->channelEntryPoints.pVirtualChannelCloseEx(rdpsnd->InitHandle, opened);
+
+ if (CHANNEL_RC_OK != error)
+ {
+ WLog_ERR(TAG, "%s pVirtualChannelCloseEx failed with %s [%08" PRIX32 "]",
+ rdpsnd_is_dyn_str(rdpsnd->dynamic), WTSErrorToString(error), error);
+ return error;
+ }
+ }
+
+ cleanup_internals(rdpsnd);
+
+ if (rdpsnd->device)
+ {
+ IFCALL(rdpsnd->device->Free, rdpsnd->device);
+ rdpsnd->device = NULL;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static void _queue_free(void* obj)
+{
+ wMessage* msg = obj;
+ if (!msg)
+ return;
+ if (msg->id != 0)
+ return;
+ wStream* s = msg->wParam;
+ Stream_Release(s);
+}
+
+static void free_internals(rdpsndPlugin* rdpsnd)
+{
+ if (!rdpsnd)
+ return;
+
+ if (rdpsnd->references > 0)
+ rdpsnd->references--;
+
+ if (rdpsnd->references > 0)
+ return;
+
+ freerdp_dsp_context_free(rdpsnd->dsp_context);
+ StreamPool_Free(rdpsnd->pool);
+ rdpsnd->pool = NULL;
+ rdpsnd->dsp_context = NULL;
+}
+
+static BOOL allocate_internals(rdpsndPlugin* rdpsnd)
+{
+ WINPR_ASSERT(rdpsnd);
+
+ if (!rdpsnd->pool)
+ {
+ rdpsnd->pool = StreamPool_New(TRUE, 4096);
+ if (!rdpsnd->pool)
+ return FALSE;
+ }
+
+ if (!rdpsnd->dsp_context)
+ {
+ rdpsnd->dsp_context = freerdp_dsp_context_new(FALSE);
+ if (!rdpsnd->dsp_context)
+ return FALSE;
+ }
+ rdpsnd->references++;
+
+ return TRUE;
+}
+
+static DWORD WINAPI play_thread(LPVOID arg)
+{
+ UINT error = CHANNEL_RC_OK;
+ rdpsndPlugin* rdpsnd = arg;
+
+ if (!rdpsnd || !rdpsnd->queue)
+ return ERROR_INVALID_PARAMETER;
+
+ while (TRUE)
+ {
+ int rc = -1;
+ wMessage message = { 0 };
+ wStream* s = NULL;
+ DWORD status = 0;
+ DWORD nCount = 0;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+
+ handles[nCount++] = MessageQueue_Event(rdpsnd->queue);
+ handles[nCount++] = freerdp_abort_event(rdpsnd->rdpcontext);
+ status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE);
+ switch (status)
+ {
+ case WAIT_OBJECT_0:
+ break;
+ default:
+ return ERROR_TIMEOUT;
+ }
+
+ rc = MessageQueue_Peek(rdpsnd->queue, &message, TRUE);
+ if (rc < 1)
+ continue;
+
+ if (message.id == WMQ_QUIT)
+ break;
+
+ s = message.wParam;
+ error = rdpsnd_recv_pdu(rdpsnd, s);
+
+ if (error)
+ return error;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpsnd_virtual_channel_event_initialized(rdpsndPlugin* rdpsnd)
+{
+ if (!rdpsnd)
+ return ERROR_INVALID_PARAMETER;
+
+ if (rdpsnd->async)
+ {
+ wObject obj = { 0 };
+
+ obj.fnObjectFree = _queue_free;
+ rdpsnd->queue = MessageQueue_New(&obj);
+ if (!rdpsnd->queue)
+ return CHANNEL_RC_NO_MEMORY;
+
+ rdpsnd->thread = CreateThread(NULL, 0, play_thread, rdpsnd, 0, NULL);
+ if (!rdpsnd->thread)
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+ }
+
+ if (!allocate_internals(rdpsnd))
+ return CHANNEL_RC_NO_MEMORY;
+
+ return CHANNEL_RC_OK;
+}
+
+void rdpsnd_virtual_channel_event_terminated(rdpsndPlugin* rdpsnd)
+{
+ if (rdpsnd)
+ {
+ if (rdpsnd->queue)
+ MessageQueue_PostQuit(rdpsnd->queue, 0);
+
+ if (rdpsnd->thread)
+ {
+ WaitForSingleObject(rdpsnd->thread, INFINITE);
+ CloseHandle(rdpsnd->thread);
+ }
+ MessageQueue_Free(rdpsnd->queue);
+
+ free_internals(rdpsnd);
+ audio_formats_free(rdpsnd->fixed_format, 1);
+ free(rdpsnd->subsystem);
+ free(rdpsnd->device_name);
+ rdpsnd->InitHandle = 0;
+ }
+
+ free(rdpsnd);
+}
+
+static VOID VCAPITYPE rdpsnd_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle,
+ UINT event, LPVOID pData,
+ UINT dataLength)
+{
+ UINT error = CHANNEL_RC_OK;
+ rdpsndPlugin* plugin = (rdpsndPlugin*)lpUserParam;
+
+ if (!plugin)
+ return;
+
+ if (plugin->InitHandle != pInitHandle)
+ {
+ WLog_ERR(TAG, "%s error no match", rdpsnd_is_dyn_str(plugin->dynamic));
+ return;
+ }
+
+ switch (event)
+ {
+ case CHANNEL_EVENT_INITIALIZED:
+ error = rdpsnd_virtual_channel_event_initialized(plugin);
+ break;
+
+ case CHANNEL_EVENT_CONNECTED:
+ error = rdpsnd_virtual_channel_event_connected(plugin, pData, dataLength);
+ break;
+
+ case CHANNEL_EVENT_DISCONNECTED:
+ error = rdpsnd_virtual_channel_event_disconnected(plugin);
+ break;
+
+ case CHANNEL_EVENT_TERMINATED:
+ rdpsnd_virtual_channel_event_terminated(plugin);
+ plugin = NULL;
+ break;
+
+ case CHANNEL_EVENT_ATTACHED:
+ plugin->attached = TRUE;
+ break;
+
+ case CHANNEL_EVENT_DETACHED:
+ plugin->attached = FALSE;
+ break;
+
+ default:
+ break;
+ }
+
+ if (error && plugin && plugin->rdpcontext)
+ {
+ char buffer[8192];
+ _snprintf(buffer, sizeof(buffer), "%s reported an error",
+ rdpsnd_is_dyn_str(plugin->dynamic));
+ setChannelError(plugin->rdpcontext, error, buffer);
+ }
+}
+
+rdpContext* freerdp_rdpsnd_get_context(rdpsndPlugin* plugin)
+{
+ if (!plugin)
+ return NULL;
+
+ return plugin->rdpcontext;
+}
+
+static rdpsndPlugin* allocatePlugin(void)
+{
+ rdpsndPlugin* rdpsnd = (rdpsndPlugin*)calloc(1, sizeof(rdpsndPlugin));
+ if (!rdpsnd)
+ goto fail;
+
+ rdpsnd->fixed_format = audio_format_new();
+ if (!rdpsnd->fixed_format)
+ goto fail;
+ rdpsnd->log = WLog_Get("com.freerdp.channels.rdpsnd.client");
+ if (!rdpsnd->log)
+ goto fail;
+
+ rdpsnd->attached = TRUE;
+ return rdpsnd;
+
+fail:
+ if (rdpsnd)
+ audio_format_free(rdpsnd->fixed_format);
+ free(rdpsnd);
+ return NULL;
+}
+/* rdpsnd is always built-in */
+FREERDP_ENTRY_POINT(BOOL VCAPITYPE rdpsnd_VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS pEntryPoints,
+ PVOID pInitHandle))
+{
+ UINT rc = 0;
+ rdpsndPlugin* rdpsnd = NULL;
+ CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx = NULL;
+
+ if (!pEntryPoints)
+ return FALSE;
+
+ rdpsnd = allocatePlugin();
+
+ if (!rdpsnd)
+ return FALSE;
+
+ rdpsnd->channelDef.options = CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP;
+ sprintf_s(rdpsnd->channelDef.name, ARRAYSIZE(rdpsnd->channelDef.name), RDPSND_CHANNEL_NAME);
+ pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints;
+
+ if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) &&
+ (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER))
+ {
+ rdpsnd->rdpcontext = pEntryPointsEx->context;
+ if (!freerdp_settings_get_bool(rdpsnd->rdpcontext->settings,
+ FreeRDP_SynchronousStaticChannels))
+ rdpsnd->async = TRUE;
+ }
+
+ CopyMemory(&(rdpsnd->channelEntryPoints), pEntryPoints,
+ sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX));
+ rdpsnd->InitHandle = pInitHandle;
+
+ WINPR_ASSERT(rdpsnd->channelEntryPoints.pVirtualChannelInitEx);
+ rc = rdpsnd->channelEntryPoints.pVirtualChannelInitEx(
+ rdpsnd, NULL, pInitHandle, &rdpsnd->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000,
+ rdpsnd_virtual_channel_init_event_ex);
+
+ if (CHANNEL_RC_OK != rc)
+ {
+ WLog_ERR(TAG, "%s pVirtualChannelInitEx failed with %s [%08" PRIX32 "]",
+ rdpsnd_is_dyn_str(FALSE), WTSErrorToString(rc), rc);
+ rdpsnd_virtual_channel_event_terminated(rdpsnd);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static UINT rdpsnd_on_open(IWTSVirtualChannelCallback* pChannelCallback)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+ rdpsndPlugin* rdpsnd = NULL;
+
+ WINPR_ASSERT(callback);
+
+ rdpsnd = (rdpsndPlugin*)callback->plugin;
+ WINPR_ASSERT(rdpsnd);
+
+ if (rdpsnd->OnOpenCalled)
+ return CHANNEL_RC_OK;
+ rdpsnd->OnOpenCalled = TRUE;
+
+ if (!allocate_internals(rdpsnd))
+ return ERROR_OUTOFMEMORY;
+
+ return rdpsnd_process_connect(rdpsnd);
+}
+
+static UINT rdpsnd_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+ rdpsndPlugin* plugin = NULL;
+ wStream* copy = NULL;
+ size_t len = 0;
+
+ len = Stream_GetRemainingLength(data);
+
+ if (!callback || !callback->plugin)
+ return ERROR_INVALID_PARAMETER;
+ plugin = (rdpsndPlugin*)callback->plugin;
+ WINPR_ASSERT(plugin);
+
+ copy = StreamPool_Take(plugin->pool, len);
+ if (!copy)
+ return ERROR_OUTOFMEMORY;
+ Stream_Copy(data, copy, len);
+ Stream_SealLength(copy);
+ Stream_SetPosition(copy, 0);
+
+ if (plugin->async)
+ {
+ if (!MessageQueue_Post(plugin->queue, NULL, 0, copy, NULL))
+ {
+ Stream_Release(copy);
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+ else
+ {
+ UINT error = rdpsnd_recv_pdu(plugin, copy);
+ if (error)
+ return error;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpsnd_on_close(IWTSVirtualChannelCallback* pChannelCallback)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+ rdpsndPlugin* rdpsnd = NULL;
+
+ WINPR_ASSERT(callback);
+
+ rdpsnd = (rdpsndPlugin*)callback->plugin;
+ WINPR_ASSERT(rdpsnd);
+
+ rdpsnd->OnOpenCalled = FALSE;
+ if (rdpsnd->device)
+ IFCALL(rdpsnd->device->Close, rdpsnd->device);
+
+ cleanup_internals(rdpsnd);
+
+ if (rdpsnd->device)
+ {
+ IFCALL(rdpsnd->device->Free, rdpsnd->device);
+ rdpsnd->device = NULL;
+ }
+
+ free_internals(rdpsnd);
+ free(pChannelCallback);
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpsnd_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
+ IWTSVirtualChannel* pChannel, BYTE* Data,
+ BOOL* pbAccept,
+ IWTSVirtualChannelCallback** ppCallback)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = NULL;
+ GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback;
+ WINPR_ASSERT(listener_callback);
+ WINPR_ASSERT(pChannel);
+ WINPR_ASSERT(ppCallback);
+ callback = (GENERIC_CHANNEL_CALLBACK*)calloc(1, sizeof(GENERIC_CHANNEL_CALLBACK));
+
+ WINPR_UNUSED(Data);
+ WINPR_UNUSED(pbAccept);
+
+ if (!callback)
+ {
+ WLog_ERR(TAG, "%s calloc failed!", rdpsnd_is_dyn_str(TRUE));
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ callback->iface.OnOpen = rdpsnd_on_open;
+ callback->iface.OnDataReceived = rdpsnd_on_data_received;
+ callback->iface.OnClose = rdpsnd_on_close;
+ callback->plugin = listener_callback->plugin;
+ callback->channel_mgr = listener_callback->channel_mgr;
+ callback->channel = pChannel;
+ listener_callback->channel_callback = callback;
+ *ppCallback = &callback->iface;
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpsnd_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr)
+{
+ UINT status = 0;
+ rdpsndPlugin* rdpsnd = (rdpsndPlugin*)pPlugin;
+ WINPR_ASSERT(rdpsnd);
+ WINPR_ASSERT(pChannelMgr);
+ if (rdpsnd->initialized)
+ {
+ WLog_ERR(TAG, "[%s] channel initialized twice, aborting", RDPSND_DVC_CHANNEL_NAME);
+ return ERROR_INVALID_DATA;
+ }
+ rdpsnd->listener_callback =
+ (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK));
+
+ if (!rdpsnd->listener_callback)
+ {
+ WLog_ERR(TAG, "%s calloc failed!", rdpsnd_is_dyn_str(TRUE));
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ rdpsnd->listener_callback->iface.OnNewChannelConnection = rdpsnd_on_new_channel_connection;
+ rdpsnd->listener_callback->plugin = pPlugin;
+ rdpsnd->listener_callback->channel_mgr = pChannelMgr;
+ status = pChannelMgr->CreateListener(pChannelMgr, RDPSND_DVC_CHANNEL_NAME, 0,
+ &rdpsnd->listener_callback->iface, &(rdpsnd->listener));
+ if (status != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "%s CreateListener failed!", rdpsnd_is_dyn_str(TRUE));
+ return status;
+ }
+
+ rdpsnd->listener->pInterface = rdpsnd->iface.pInterface;
+ status = rdpsnd_virtual_channel_event_initialized(rdpsnd);
+
+ rdpsnd->initialized = status == CHANNEL_RC_OK;
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_plugin_terminated(IWTSPlugin* pPlugin)
+{
+ rdpsndPlugin* rdpsnd = (rdpsndPlugin*)pPlugin;
+ if (rdpsnd)
+ {
+ if (rdpsnd->listener_callback)
+ {
+ IWTSVirtualChannelManager* mgr = rdpsnd->listener_callback->channel_mgr;
+ if (mgr)
+ IFCALL(mgr->DestroyListener, mgr, rdpsnd->listener);
+ }
+ free(rdpsnd->listener_callback);
+ free(rdpsnd->iface.pInterface);
+ }
+ rdpsnd_virtual_channel_event_terminated(rdpsnd);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT rdpsnd_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
+{
+ UINT error = CHANNEL_RC_OK;
+ rdpsndPlugin* rdpsnd = NULL;
+
+ WINPR_ASSERT(pEntryPoints);
+ WINPR_ASSERT(pEntryPoints->GetPlugin);
+
+ rdpsnd = (rdpsndPlugin*)pEntryPoints->GetPlugin(pEntryPoints, RDPSND_CHANNEL_NAME);
+
+ if (!rdpsnd)
+ {
+ IWTSPlugin* iface = NULL;
+ union
+ {
+ const void* cev;
+ void* ev;
+ } cnv;
+
+ rdpsnd = allocatePlugin();
+ if (!rdpsnd)
+ {
+ WLog_ERR(TAG, "%s calloc failed!", rdpsnd_is_dyn_str(TRUE));
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ iface = &rdpsnd->iface;
+ iface->Initialize = rdpsnd_plugin_initialize;
+ iface->Connected = NULL;
+ iface->Disconnected = NULL;
+ iface->Terminated = rdpsnd_plugin_terminated;
+
+ rdpsnd->dynamic = TRUE;
+
+ WINPR_ASSERT(pEntryPoints->GetRdpContext);
+ rdpsnd->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints);
+
+ if (!freerdp_settings_get_bool(rdpsnd->rdpcontext->settings,
+ FreeRDP_SynchronousDynamicChannels))
+ rdpsnd->async = TRUE;
+
+ /* user data pointer is not const, cast to avoid warning. */
+ cnv.cev = pEntryPoints->GetPluginData(pEntryPoints);
+ WINPR_ASSERT(pEntryPoints->GetPluginData);
+ rdpsnd->channelEntryPoints.pExtendedData = cnv.ev;
+
+ error = pEntryPoints->RegisterPlugin(pEntryPoints, RDPSND_CHANNEL_NAME, iface);
+ }
+ else
+ {
+ WLog_ERR(TAG, "%s could not get rdpsnd Plugin.", rdpsnd_is_dyn_str(TRUE));
+ return CHANNEL_RC_BAD_CHANNEL;
+ }
+
+ return error;
+}
diff --git a/channels/rdpsnd/client/rdpsnd_main.h b/channels/rdpsnd/client/rdpsnd_main.h
new file mode 100644
index 0000000..33adfcd
--- /dev/null
+++ b/channels/rdpsnd/client/rdpsnd_main.h
@@ -0,0 +1,41 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Output Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2012-2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPSND_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_RDPSND_CLIENT_MAIN_H
+
+#include <freerdp/api.h>
+#include <freerdp/svc.h>
+#include <freerdp/addin.h>
+#include <freerdp/client/rdpsnd.h>
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("rdpsnd.client")
+
+#if defined(WITH_DEBUG_SND)
+#define DEBUG_SND(...) WLog_DBG(TAG, __VA_ARGS__)
+#else
+#define DEBUG_SND(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#endif /* FREERDP_CHANNEL_RDPSND_CLIENT_MAIN_H */
diff --git a/channels/rdpsnd/client/sndio/CMakeLists.txt b/channels/rdpsnd/client/sndio/CMakeLists.txt
new file mode 100644
index 0000000..78b9c06
--- /dev/null
+++ b/channels/rdpsnd/client/sndio/CMakeLists.txt
@@ -0,0 +1,36 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
+# Copyright (c) 2020 Ingo Feinerer <feinerer@logic.at>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("rdpsnd" "sndio" "")
+
+find_package(SNDIO REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ rdpsnd_sndio.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${SNDIO_LIBRARIES}
+)
+
+include_directories(..)
+include_directories(${SNDIO_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/rdpsnd/client/sndio/rdpsnd_sndio.c b/channels/rdpsnd/client/sndio/rdpsnd_sndio.c
new file mode 100644
index 0000000..4414653
--- /dev/null
+++ b/channels/rdpsnd/client/sndio/rdpsnd_sndio.c
@@ -0,0 +1,217 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Output Virtual Channel
+ *
+ * Copyright 2019 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2019 Thincast Technologies GmbH
+ * Copyright 2020 Ingo Feinerer <feinerer@logic.at>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <sndio.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+#include <winpr/cmdline.h>
+
+#include <freerdp/types.h>
+
+#include "rdpsnd_main.h"
+
+typedef struct
+{
+ rdpsndDevicePlugin device;
+
+ struct sio_hdl* hdl;
+ struct sio_par par;
+} rdpsndSndioPlugin;
+
+static BOOL rdpsnd_sndio_open(rdpsndDevicePlugin* device, AUDIO_FORMAT* format, int latency)
+{
+ rdpsndSndioPlugin* sndio = (rdpsndSndioPlugin*)device;
+
+ if (device == NULL || format == NULL)
+ return FALSE;
+
+ if (sndio->hdl != NULL)
+ return TRUE;
+
+ sndio->hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0);
+ if (sndio->hdl == NULL)
+ {
+ WLog_ERR(TAG, "could not open audio device");
+ return FALSE;
+ }
+
+ sio_initpar(&sndio->par);
+ sndio->par.bits = format->wBitsPerSample;
+ sndio->par.pchan = format->nChannels;
+ sndio->par.rate = format->nSamplesPerSec;
+ if (!sio_setpar(sndio->hdl, &sndio->par))
+ {
+ WLog_ERR(TAG, "could not set audio parameters");
+ return FALSE;
+ }
+ if (!sio_getpar(sndio->hdl, &sndio->par))
+ {
+ WLog_ERR(TAG, "could not get audio parameters");
+ return FALSE;
+ }
+
+ if (!sio_start(sndio->hdl))
+ {
+ WLog_ERR(TAG, "could not start audio device");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void rdpsnd_sndio_close(rdpsndDevicePlugin* device)
+{
+ rdpsndSndioPlugin* sndio = (rdpsndSndioPlugin*)device;
+
+ if (device == NULL)
+ return;
+
+ if (sndio->hdl != NULL)
+ {
+ sio_stop(sndio->hdl);
+ sio_close(sndio->hdl);
+ sndio->hdl = NULL;
+ }
+}
+
+static BOOL rdpsnd_sndio_set_volume(rdpsndDevicePlugin* device, UINT32 value)
+{
+ rdpsndSndioPlugin* sndio = (rdpsndSndioPlugin*)device;
+
+ if (device == NULL || sndio->hdl == NULL)
+ return FALSE;
+
+ /*
+ * Low-order word contains the left-channel volume setting.
+ * We ignore the right-channel volume setting in the high-order word.
+ */
+ return sio_setvol(sndio->hdl, ((value & 0xFFFF) * SIO_MAXVOL) / 0xFFFF);
+}
+
+static void rdpsnd_sndio_free(rdpsndDevicePlugin* device)
+{
+ rdpsndSndioPlugin* sndio = (rdpsndSndioPlugin*)device;
+
+ if (device == NULL)
+ return;
+
+ rdpsnd_sndio_close(device);
+ free(sndio);
+}
+
+static BOOL rdpsnd_sndio_format_supported(rdpsndDevicePlugin* device, AUDIO_FORMAT* format)
+{
+ if (format == NULL)
+ return FALSE;
+
+ return (format->wFormatTag == WAVE_FORMAT_PCM);
+}
+
+static void rdpsnd_sndio_play(rdpsndDevicePlugin* device, BYTE* data, int size)
+{
+ rdpsndSndioPlugin* sndio = (rdpsndSndioPlugin*)device;
+
+ if (device == NULL || sndio->hdl == NULL)
+ return;
+
+ sio_write(sndio->hdl, data, size);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_sndio_parse_addin_args(rdpsndDevicePlugin* device, ADDIN_ARGV* args)
+{
+ int status;
+ DWORD flags;
+ COMMAND_LINE_ARGUMENT_A* arg;
+ rdpsndSndioPlugin* sndio = (rdpsndSndioPlugin*)device;
+ COMMAND_LINE_ARGUMENT_A rdpsnd_sndio_args[] = { { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
+ flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ status = CommandLineParseArgumentsA(args->argc, (const char**)args->argv, rdpsnd_sndio_args,
+ flags, sndio, NULL, NULL);
+
+ if (status < 0)
+ return ERROR_INVALID_DATA;
+
+ arg = rdpsnd_sndio_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT sndio_freerdp_rdpsnd_client_subsystem_entry(
+ PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ ADDIN_ARGV* args;
+ rdpsndSndioPlugin* sndio;
+ UINT ret = CHANNEL_RC_OK;
+ sndio = (rdpsndSndioPlugin*)calloc(1, sizeof(rdpsndSndioPlugin));
+
+ if (sndio == NULL)
+ return CHANNEL_RC_NO_MEMORY;
+
+ sndio->device.Open = rdpsnd_sndio_open;
+ sndio->device.FormatSupported = rdpsnd_sndio_format_supported;
+ sndio->device.SetVolume = rdpsnd_sndio_set_volume;
+ sndio->device.Play = rdpsnd_sndio_play;
+ sndio->device.Close = rdpsnd_sndio_close;
+ sndio->device.Free = rdpsnd_sndio_free;
+ args = pEntryPoints->args;
+
+ if (args->argc > 1)
+ {
+ ret = rdpsnd_sndio_parse_addin_args((rdpsndDevicePlugin*)sndio, args);
+
+ if (ret != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "error parsing arguments");
+ goto error;
+ }
+ }
+
+ pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, &sndio->device);
+ return ret;
+error:
+ rdpsnd_sndio_free(&sndio->device);
+ return ret;
+}
diff --git a/channels/rdpsnd/client/winmm/CMakeLists.txt b/channels/rdpsnd/client/winmm/CMakeLists.txt
new file mode 100644
index 0000000..6e9d8d1
--- /dev/null
+++ b/channels/rdpsnd/client/winmm/CMakeLists.txt
@@ -0,0 +1,32 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("rdpsnd" "winmm" "")
+
+set(${MODULE_PREFIX}_SRCS
+ rdpsnd_winmm.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ winmm.lib
+)
+
+include_directories(..)
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/rdpsnd/client/winmm/rdpsnd_winmm.c b/channels/rdpsnd/client/winmm/rdpsnd_winmm.c
new file mode 100644
index 0000000..2ba0654
--- /dev/null
+++ b/channels/rdpsnd/client/winmm/rdpsnd_winmm.c
@@ -0,0 +1,347 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Output Virtual Channel
+ *
+ * Copyright 2009-2012 Jay Sorg
+ * Copyright 2010-2012 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <windows.h>
+#include <mmsystem.h>
+
+#include <winpr/crt.h>
+#include <winpr/cmdline.h>
+#include <winpr/sysinfo.h>
+
+#include <freerdp/types.h>
+#include <freerdp/channels/log.h>
+
+#include "rdpsnd_main.h"
+
+typedef struct
+{
+ rdpsndDevicePlugin device;
+
+ HWAVEOUT hWaveOut;
+ WAVEFORMATEX format;
+ UINT32 volume;
+ wLog* log;
+ UINT32 latency;
+ HANDLE hThread;
+ DWORD threadId;
+ CRITICAL_SECTION cs;
+} rdpsndWinmmPlugin;
+
+static BOOL rdpsnd_winmm_convert_format(const AUDIO_FORMAT* in, WAVEFORMATEX* out)
+{
+ if (!in || !out)
+ return FALSE;
+
+ ZeroMemory(out, sizeof(WAVEFORMATEX));
+ out->wFormatTag = WAVE_FORMAT_PCM;
+ out->nChannels = in->nChannels;
+ out->nSamplesPerSec = in->nSamplesPerSec;
+
+ switch (in->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ out->wBitsPerSample = in->wBitsPerSample;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ out->nBlockAlign = out->nChannels * out->wBitsPerSample / 8;
+ out->nAvgBytesPerSec = out->nSamplesPerSec * out->nBlockAlign;
+ return TRUE;
+}
+
+static BOOL rdpsnd_winmm_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
+ UINT32 latency)
+{
+ rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
+
+ winmm->latency = latency;
+ if (!rdpsnd_winmm_convert_format(format, &winmm->format))
+ return FALSE;
+
+ return TRUE;
+}
+
+static DWORD WINAPI waveOutProc(LPVOID lpParameter)
+{
+ MSG msg;
+ rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)lpParameter;
+ while (GetMessage(&msg, NULL, 0, 0))
+ {
+ if (msg.message == MM_WOM_CLOSE)
+ {
+ /* device was closed - exit thread */
+ break;
+ }
+ else if (msg.message == MM_WOM_DONE)
+ {
+ /* free buffer */
+ LPWAVEHDR waveHdr = (LPWAVEHDR)msg.lParam;
+ EnterCriticalSection(&winmm->cs);
+ waveOutUnprepareHeader((HWAVEOUT)msg.wParam, waveHdr, sizeof(WAVEHDR));
+ LeaveCriticalSection(&winmm->cs);
+ free(waveHdr->lpData);
+ free(waveHdr);
+ }
+ }
+
+ return 0;
+}
+
+static BOOL rdpsnd_winmm_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
+ UINT32 latency)
+{
+ MMRESULT mmResult;
+ rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
+
+ if (winmm->hWaveOut)
+ return TRUE;
+
+ if (!rdpsnd_winmm_set_format(device, format, latency))
+ return FALSE;
+
+ winmm->hThread = CreateThread(NULL, 0, waveOutProc, winmm, 0, &winmm->threadId);
+ if (!winmm->hThread)
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "CreateThread failed: %" PRIu32 "", GetLastError());
+ return FALSE;
+ }
+
+ mmResult = waveOutOpen(&winmm->hWaveOut, WAVE_MAPPER, &winmm->format,
+ (DWORD_PTR)winmm->threadId, 0, CALLBACK_THREAD);
+
+ if (mmResult != MMSYSERR_NOERROR)
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "waveOutOpen failed: %" PRIu32 "", mmResult);
+ return FALSE;
+ }
+
+ mmResult = waveOutSetVolume(winmm->hWaveOut, winmm->volume);
+
+ if (mmResult != MMSYSERR_NOERROR)
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "waveOutSetVolume failed: %" PRIu32 "", mmResult);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void rdpsnd_winmm_close(rdpsndDevicePlugin* device)
+{
+ MMRESULT mmResult;
+ rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
+
+ if (winmm->hWaveOut)
+ {
+ EnterCriticalSection(&winmm->cs);
+
+ mmResult = waveOutReset(winmm->hWaveOut);
+ if (mmResult != MMSYSERR_NOERROR)
+ WLog_Print(winmm->log, WLOG_ERROR, "waveOutReset failure: %" PRIu32 "", mmResult);
+
+ mmResult = waveOutClose(winmm->hWaveOut);
+ if (mmResult != MMSYSERR_NOERROR)
+ WLog_Print(winmm->log, WLOG_ERROR, "waveOutClose failure: %" PRIu32 "", mmResult);
+
+ LeaveCriticalSection(&winmm->cs);
+
+ winmm->hWaveOut = NULL;
+ }
+
+ if (winmm->hThread)
+ {
+ WaitForSingleObject(winmm->hThread, INFINITE);
+ CloseHandle(winmm->hThread);
+ winmm->hThread = NULL;
+ }
+}
+
+static void rdpsnd_winmm_free(rdpsndDevicePlugin* device)
+{
+ rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
+
+ if (winmm)
+ {
+ rdpsnd_winmm_close(device);
+ DeleteCriticalSection(&winmm->cs);
+ free(winmm);
+ }
+}
+
+static BOOL rdpsnd_winmm_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
+{
+ MMRESULT result;
+ WAVEFORMATEX out;
+
+ WINPR_UNUSED(device);
+ if (rdpsnd_winmm_convert_format(format, &out))
+ {
+ result = waveOutOpen(NULL, WAVE_MAPPER, &out, 0, 0, WAVE_FORMAT_QUERY);
+
+ if (result == MMSYSERR_NOERROR)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static UINT32 rdpsnd_winmm_get_volume(rdpsndDevicePlugin* device)
+{
+ MMRESULT mmResult;
+ DWORD dwVolume = UINT32_MAX;
+ rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
+
+ if (!winmm->hWaveOut)
+ return dwVolume;
+
+ mmResult = waveOutGetVolume(winmm->hWaveOut, &dwVolume);
+ if (mmResult != MMSYSERR_NOERROR)
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "waveOutGetVolume failure: %" PRIu32 "", mmResult);
+ dwVolume = UINT32_MAX;
+ }
+ return dwVolume;
+}
+
+static BOOL rdpsnd_winmm_set_volume(rdpsndDevicePlugin* device, UINT32 value)
+{
+ MMRESULT mmResult;
+ rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
+ winmm->volume = value;
+
+ if (!winmm->hWaveOut)
+ return TRUE;
+
+ mmResult = waveOutSetVolume(winmm->hWaveOut, value);
+ if (mmResult != MMSYSERR_NOERROR)
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "waveOutGetVolume failure: %" PRIu32 "", mmResult);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static UINT rdpsnd_winmm_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
+{
+ MMRESULT mmResult;
+ LPWAVEHDR lpWaveHdr;
+ rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
+
+ if (!winmm->hWaveOut)
+ return 0;
+
+ if (size > UINT32_MAX)
+ return 0;
+
+ lpWaveHdr = (LPWAVEHDR)calloc(1, sizeof(WAVEHDR));
+ if (!lpWaveHdr)
+ return 0;
+
+ lpWaveHdr->dwFlags = 0;
+ lpWaveHdr->dwLoops = 0;
+ lpWaveHdr->lpData = malloc(size);
+ if (!lpWaveHdr->lpData)
+ goto fail;
+ memcpy(lpWaveHdr->lpData, data, size);
+ lpWaveHdr->dwBufferLength = (DWORD)size;
+
+ EnterCriticalSection(&winmm->cs);
+
+ mmResult = waveOutPrepareHeader(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
+ if (mmResult != MMSYSERR_NOERROR)
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "waveOutPrepareHeader failure: %" PRIu32 "", mmResult);
+ goto failCS;
+ }
+
+ mmResult = waveOutWrite(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
+ if (mmResult != MMSYSERR_NOERROR)
+ {
+ WLog_Print(winmm->log, WLOG_ERROR, "waveOutWrite failure: %" PRIu32 "", mmResult);
+ waveOutUnprepareHeader(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
+ goto failCS;
+ }
+
+ LeaveCriticalSection(&winmm->cs);
+ return winmm->latency;
+failCS:
+ LeaveCriticalSection(&winmm->cs);
+fail:
+ if (lpWaveHdr)
+ free(lpWaveHdr->lpData);
+ free(lpWaveHdr);
+ return 0;
+}
+
+static void rdpsnd_winmm_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
+{
+ WINPR_UNUSED(device);
+ WINPR_UNUSED(args);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT winmm_freerdp_rdpsnd_client_subsystem_entry(
+ PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ const ADDIN_ARGV* args;
+ rdpsndWinmmPlugin* winmm;
+
+ if (waveOutGetNumDevs() == 0)
+ {
+ WLog_Print(WLog_Get(TAG), WLOG_ERROR, "No sound playback device available!");
+ return ERROR_DEVICE_NOT_AVAILABLE;
+ }
+
+ winmm = (rdpsndWinmmPlugin*)calloc(1, sizeof(rdpsndWinmmPlugin));
+ if (!winmm)
+ return CHANNEL_RC_NO_MEMORY;
+
+ winmm->device.Open = rdpsnd_winmm_open;
+ winmm->device.FormatSupported = rdpsnd_winmm_format_supported;
+ winmm->device.GetVolume = rdpsnd_winmm_get_volume;
+ winmm->device.SetVolume = rdpsnd_winmm_set_volume;
+ winmm->device.Play = rdpsnd_winmm_play;
+ winmm->device.Close = rdpsnd_winmm_close;
+ winmm->device.Free = rdpsnd_winmm_free;
+ winmm->log = WLog_Get(TAG);
+ InitializeCriticalSection(&winmm->cs);
+
+ args = pEntryPoints->args;
+ rdpsnd_winmm_parse_addin_args(&winmm->device, args);
+ winmm->volume = 0xFFFFFFFF;
+ pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)winmm);
+ return CHANNEL_RC_OK;
+}
diff --git a/channels/rdpsnd/common/CMakeLists.txt b/channels/rdpsnd/common/CMakeLists.txt
new file mode 100644
index 0000000..763cb71
--- /dev/null
+++ b/channels/rdpsnd/common/CMakeLists.txt
@@ -0,0 +1,27 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2018 Armin Novak <armin.novak@thincast.com>
+# Copyright 2018 Thincast Technologies GmbH
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set(SRCS
+ rdpsnd_common.h
+ rdpsnd_common.c)
+
+# Library currently header only
+add_library(rdpsnd-common STATIC ${SRCS})
+
+channel_install(rdpsnd-common ${FREERDP_ADDIN_PATH} "FreeRDPTargets")
+
diff --git a/channels/rdpsnd/common/rdpsnd_common.c b/channels/rdpsnd/common/rdpsnd_common.c
new file mode 100644
index 0000000..a420beb
--- /dev/null
+++ b/channels/rdpsnd/common/rdpsnd_common.c
@@ -0,0 +1,21 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Server Audio Virtual Channel
+ *
+ * Copyright 2018 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2018 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "rdpsnd_common.h"
diff --git a/channels/rdpsnd/common/rdpsnd_common.h b/channels/rdpsnd/common/rdpsnd_common.h
new file mode 100644
index 0000000..6afcbc7
--- /dev/null
+++ b/channels/rdpsnd/common/rdpsnd_common.h
@@ -0,0 +1,43 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Server Audio Virtual Channel
+ *
+ * Copyright 2018 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2018 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPSND_COMMON_MAIN_H
+#define FREERDP_CHANNEL_RDPSND_COMMON_MAIN_H
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+#include <freerdp/codec/dsp.h>
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/server/rdpsnd.h>
+
+typedef enum
+{
+ CHANNEL_VERSION_WIN_XP = 0x02,
+ CHANNEL_VERSION_WIN_XP_SP1 = 0x05,
+ CHANNEL_VERSION_WIN_VISTA = 0x05,
+ CHANNEL_VERSION_WIN_7 = 0x06,
+ CHANNEL_VERSION_WIN_8 = 0x08,
+ CHANNEL_VERSION_WIN_MAX = CHANNEL_VERSION_WIN_8
+} RdpSndChannelVersion;
+
+#endif /* FREERDP_CHANNEL_RDPSND_COMMON_MAIN_H */
diff --git a/channels/rdpsnd/server/CMakeLists.txt b/channels/rdpsnd/server/CMakeLists.txt
new file mode 100644
index 0000000..881b479
--- /dev/null
+++ b/channels/rdpsnd/server/CMakeLists.txt
@@ -0,0 +1,29 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_server("rdpsnd")
+
+set(${MODULE_PREFIX}_SRCS
+ rdpsnd_main.c
+ rdpsnd_main.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ rdpsnd-common
+)
+
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry")
diff --git a/channels/rdpsnd/server/rdpsnd_main.c b/channels/rdpsnd/server/rdpsnd_main.c
new file mode 100644
index 0000000..73f970c
--- /dev/null
+++ b/channels/rdpsnd/server/rdpsnd_main.c
@@ -0,0 +1,1231 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Server Audio Virtual Channel
+ *
+ * Copyright 2012 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/log.h>
+
+#include "rdpsnd_common.h"
+#include "rdpsnd_main.h"
+
+static wStream* rdpsnd_server_get_buffer(RdpsndServerContext* context)
+{
+ wStream* s = NULL;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ s = context->priv->rdpsnd_pdu;
+ Stream_SetPosition(s, 0);
+ return s;
+}
+
+/**
+ * Send Server Audio Formats and Version PDU (2.2.2.1)
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_server_send_formats(RdpsndServerContext* context)
+{
+ wStream* s = rdpsnd_server_get_buffer(context);
+ BOOL status = FALSE;
+ ULONG written = 0;
+
+ if (!Stream_EnsureRemainingCapacity(s, 24))
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write_UINT8(s, SNDC_FORMATS);
+ Stream_Write_UINT8(s, 0);
+ Stream_Seek_UINT16(s);
+ Stream_Write_UINT32(s, 0); /* dwFlags */
+ Stream_Write_UINT32(s, 0); /* dwVolume */
+ Stream_Write_UINT32(s, 0); /* dwPitch */
+ Stream_Write_UINT16(s, 0); /* wDGramPort */
+ Stream_Write_UINT16(s, context->num_server_formats); /* wNumberOfFormats */
+ Stream_Write_UINT8(s, context->block_no); /* cLastBlockConfirmed */
+ Stream_Write_UINT16(s, CHANNEL_VERSION_WIN_MAX); /* wVersion */
+ Stream_Write_UINT8(s, 0); /* bPad */
+
+ for (size_t i = 0; i < context->num_server_formats; i++)
+ {
+ const AUDIO_FORMAT* format = &context->server_formats[i];
+
+ if (!audio_format_write(s, format))
+ goto fail;
+ }
+
+ const size_t pos = Stream_GetPosition(s);
+ Stream_SetPosition(s, 2);
+ Stream_Write_UINT16(s, pos - 4);
+ Stream_SetPosition(s, pos);
+
+ WINPR_ASSERT(context->priv);
+ status = WTSVirtualChannelWrite(context->priv->ChannelHandle, (PCHAR)Stream_Buffer(s),
+ Stream_GetPosition(s), &written);
+ Stream_SetPosition(s, 0);
+fail:
+ return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR;
+}
+
+/**
+ * Read Wave Confirm PDU (2.2.3.8) and handle callback
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_server_recv_waveconfirm(RdpsndServerContext* context, wStream* s)
+{
+ UINT16 timestamp = 0;
+ BYTE confirmBlockNum = 0;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, timestamp);
+ Stream_Read_UINT8(s, confirmBlockNum);
+ Stream_Seek_UINT8(s);
+ IFCALLRET(context->ConfirmBlock, error, context, confirmBlockNum, timestamp);
+
+ if (error)
+ WLog_ERR(TAG, "context->ConfirmBlock failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Read Training Confirm PDU (2.2.3.2) and handle callback
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_server_recv_trainingconfirm(RdpsndServerContext* context, wStream* s)
+{
+ UINT16 timestamp = 0;
+ UINT16 packsize = 0;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, timestamp);
+ Stream_Read_UINT16(s, packsize);
+
+ IFCALLRET(context->TrainingConfirm, error, context, timestamp, packsize);
+ if (error)
+ WLog_ERR(TAG, "context->TrainingConfirm failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+/**
+ * Read Quality Mode PDU (2.2.2.3)
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_server_recv_quality_mode(RdpsndServerContext* context, wStream* s)
+{
+ WINPR_ASSERT(context);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ {
+ WLog_ERR(TAG, "not enough data in stream!");
+ return ERROR_INVALID_DATA;
+ }
+
+ Stream_Read_UINT16(s, context->qualityMode); /* wQualityMode */
+ Stream_Seek_UINT16(s); /* Reserved */
+
+ WLog_DBG(TAG, "Client requested sound quality: 0x%04" PRIX16 "", context->qualityMode);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Read Client Audio Formats and Version PDU (2.2.2.2)
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_server_recv_formats(RdpsndServerContext* context, wStream* s)
+{
+ UINT16 num_known_format = 0;
+ UINT16 udpPort = 0;
+ BYTE lastblock = 0;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 20))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, context->capsFlags); /* dwFlags */
+ Stream_Read_UINT32(s, context->initialVolume); /* dwVolume */
+ Stream_Read_UINT32(s, context->initialPitch); /* dwPitch */
+ Stream_Read_UINT16(s, udpPort); /* wDGramPort */
+ Stream_Read_UINT16(s, context->num_client_formats); /* wNumberOfFormats */
+ Stream_Read_UINT8(s, lastblock); /* cLastBlockConfirmed */
+ Stream_Read_UINT16(s, context->clientVersion); /* wVersion */
+ Stream_Seek_UINT8(s); /* bPad */
+
+ /* this check is only a guess as cbSize can influence the size of a format record */
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, context->num_client_formats, 18ull))
+ return ERROR_INVALID_DATA;
+
+ if (!context->num_client_formats)
+ {
+ WLog_ERR(TAG, "client doesn't support any format!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ context->client_formats = audio_formats_new(context->num_client_formats);
+
+ if (!context->client_formats)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ for (UINT16 i = 0; i < context->num_client_formats; i++)
+ {
+ AUDIO_FORMAT* format = &context->client_formats[i];
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 18))
+ {
+ WLog_ERR(TAG, "not enough data in stream!");
+ error = ERROR_INVALID_DATA;
+ goto out_free;
+ }
+
+ Stream_Read_UINT16(s, format->wFormatTag);
+ Stream_Read_UINT16(s, format->nChannels);
+ Stream_Read_UINT32(s, format->nSamplesPerSec);
+ Stream_Read_UINT32(s, format->nAvgBytesPerSec);
+ Stream_Read_UINT16(s, format->nBlockAlign);
+ Stream_Read_UINT16(s, format->wBitsPerSample);
+ Stream_Read_UINT16(s, format->cbSize);
+
+ if (format->cbSize > 0)
+ {
+ if (!Stream_SafeSeek(s, format->cbSize))
+ {
+ WLog_ERR(TAG, "Stream_SafeSeek failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto out_free;
+ }
+ }
+
+ if (format->wFormatTag != 0)
+ {
+ // lets call this a known format
+ // TODO: actually look through our own list of known formats
+ num_known_format++;
+ }
+ }
+
+ if (!context->num_client_formats)
+ {
+ WLog_ERR(TAG, "client doesn't support any known format!");
+ goto out_free;
+ }
+
+ return CHANNEL_RC_OK;
+out_free:
+ free(context->client_formats);
+ return error;
+}
+
+static DWORD WINAPI rdpsnd_server_thread(LPVOID arg)
+{
+ DWORD nCount = 0;
+ DWORD status = 0;
+ HANDLE events[2] = { 0 };
+ RdpsndServerContext* context = (RdpsndServerContext*)arg;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ events[nCount++] = context->priv->channelEvent;
+ events[nCount++] = context->priv->StopEvent;
+
+ WINPR_ASSERT(nCount <= ARRAYSIZE(events));
+
+ while (TRUE)
+ {
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error);
+ break;
+ }
+
+ status = WaitForSingleObject(context->priv->StopEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
+ break;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ if ((error = rdpsnd_server_handle_messages(context)))
+ {
+ WLog_ERR(TAG, "rdpsnd_server_handle_messages failed with error %" PRIu32 "", error);
+ break;
+ }
+ }
+
+ if (error && context->rdpcontext)
+ setChannelError(context->rdpcontext, error, "rdpsnd_server_thread reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_server_initialize(RdpsndServerContext* context, BOOL ownThread)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ context->priv->ownThread = ownThread;
+ return context->Start(context);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_server_select_format(RdpsndServerContext* context, UINT16 client_format_index)
+{
+ size_t bs = 0;
+ size_t out_buffer_size = 0;
+ AUDIO_FORMAT* format = NULL;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if ((client_format_index >= context->num_client_formats) || (!context->src_format))
+ {
+ WLog_ERR(TAG, "index %" PRIu16 " is not correct.", client_format_index);
+ return ERROR_INVALID_DATA;
+ }
+
+ EnterCriticalSection(&context->priv->lock);
+ context->priv->src_bytes_per_sample = context->src_format->wBitsPerSample / 8;
+ context->priv->src_bytes_per_frame =
+ context->priv->src_bytes_per_sample * context->src_format->nChannels;
+ context->selected_client_format = client_format_index;
+ format = &context->client_formats[client_format_index];
+
+ if (format->nSamplesPerSec == 0)
+ {
+ WLog_ERR(TAG, "invalid Client Sound Format!!");
+ error = ERROR_INVALID_DATA;
+ goto out;
+ }
+
+ if (context->latency <= 0)
+ context->latency = 50;
+
+ context->priv->out_frames = context->src_format->nSamplesPerSec * context->latency / 1000;
+
+ if (context->priv->out_frames < 1)
+ context->priv->out_frames = 1;
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_DVI_ADPCM:
+ bs = (format->nBlockAlign - 4 * format->nChannels) * 4;
+ context->priv->out_frames -= context->priv->out_frames % bs;
+
+ if (context->priv->out_frames < bs)
+ context->priv->out_frames = bs;
+
+ break;
+
+ case WAVE_FORMAT_ADPCM:
+ bs = (format->nBlockAlign - 7 * format->nChannels) * 2 / format->nChannels + 2;
+ context->priv->out_frames -= context->priv->out_frames % bs;
+
+ if (context->priv->out_frames < bs)
+ context->priv->out_frames = bs;
+
+ break;
+ }
+
+ context->priv->out_pending_frames = 0;
+ out_buffer_size = context->priv->out_frames * context->priv->src_bytes_per_frame;
+
+ if (context->priv->out_buffer_size < out_buffer_size)
+ {
+ BYTE* newBuffer = NULL;
+ newBuffer = (BYTE*)realloc(context->priv->out_buffer, out_buffer_size);
+
+ if (!newBuffer)
+ {
+ WLog_ERR(TAG, "realloc failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ context->priv->out_buffer = newBuffer;
+ context->priv->out_buffer_size = out_buffer_size;
+ }
+
+ freerdp_dsp_context_reset(context->priv->dsp_context, format, 0u);
+out:
+ LeaveCriticalSection(&context->priv->lock);
+ return error;
+}
+
+/**
+ * Send Training PDU (2.2.3.1)
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_server_training(RdpsndServerContext* context, UINT16 timestamp, UINT16 packsize,
+ BYTE* data)
+{
+ size_t end = 0;
+ ULONG written = 0;
+ BOOL status = 0;
+ wStream* s = rdpsnd_server_get_buffer(context);
+
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return ERROR_INTERNAL_ERROR;
+
+ Stream_Write_UINT8(s, SNDC_TRAINING);
+ Stream_Write_UINT8(s, 0);
+ Stream_Seek_UINT16(s);
+ Stream_Write_UINT16(s, timestamp);
+ Stream_Write_UINT16(s, packsize);
+
+ if (packsize > 0)
+ {
+ if (!Stream_EnsureRemainingCapacity(s, packsize))
+ {
+ Stream_SetPosition(s, 0);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Write(s, data, packsize);
+ }
+
+ end = Stream_GetPosition(s);
+ Stream_SetPosition(s, 2);
+ Stream_Write_UINT16(s, end - 4);
+
+ status = WTSVirtualChannelWrite(context->priv->ChannelHandle, (PCHAR)Stream_Buffer(s), end,
+ &written);
+
+ Stream_SetPosition(s, 0);
+
+ return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR;
+}
+
+static BOOL rdpsnd_server_align_wave_pdu(wStream* s, UINT32 alignment)
+{
+ size_t size = 0;
+ Stream_SealLength(s);
+ size = Stream_Length(s);
+
+ if ((size % alignment) != 0)
+ {
+ size_t offset = alignment - size % alignment;
+
+ if (!Stream_EnsureRemainingCapacity(s, offset))
+ return FALSE;
+
+ Stream_Zero(s, offset);
+ }
+
+ Stream_SealLength(s);
+ return TRUE;
+}
+
+/**
+ * Function description
+ * context->priv->lock should be obtained before calling this function
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_server_send_wave_pdu(RdpsndServerContext* context, UINT16 wTimestamp)
+{
+ size_t length = 0;
+ size_t start = 0;
+ size_t end = 0;
+ const BYTE* src = NULL;
+ AUDIO_FORMAT* format = NULL;
+ ULONG written = 0;
+ UINT error = CHANNEL_RC_OK;
+ wStream* s = rdpsnd_server_get_buffer(context);
+
+ if (context->selected_client_format > context->num_client_formats)
+ return ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(context->client_formats);
+
+ format = &context->client_formats[context->selected_client_format];
+ /* WaveInfo PDU */
+ Stream_SetPosition(s, 0);
+
+ if (!Stream_EnsureRemainingCapacity(s, 16))
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write_UINT8(s, SNDC_WAVE); /* msgType */
+ Stream_Write_UINT8(s, 0); /* bPad */
+ Stream_Write_UINT16(s, 0); /* BodySize */
+ Stream_Write_UINT16(s, wTimestamp); /* wTimeStamp */
+ Stream_Write_UINT16(s, context->selected_client_format); /* wFormatNo */
+ Stream_Write_UINT8(s, context->block_no); /* cBlockNo */
+ Stream_Seek(s, 3); /* bPad */
+ start = Stream_GetPosition(s);
+ src = context->priv->out_buffer;
+ length = 1ull * context->priv->out_pending_frames * context->priv->src_bytes_per_frame;
+
+ if (!freerdp_dsp_encode(context->priv->dsp_context, context->src_format, src, length, s))
+ return ERROR_INTERNAL_ERROR;
+ else
+ {
+ /* Set stream size */
+ if (!rdpsnd_server_align_wave_pdu(s, format->nBlockAlign))
+ return ERROR_INTERNAL_ERROR;
+
+ end = Stream_GetPosition(s);
+ Stream_SetPosition(s, 2);
+ Stream_Write_UINT16(s, end - start + 8);
+ Stream_SetPosition(s, end);
+
+ if (!WTSVirtualChannelWrite(context->priv->ChannelHandle, (PCHAR)Stream_Buffer(s),
+ start + 4, &written))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
+ error = ERROR_INTERNAL_ERROR;
+ }
+ }
+
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ Stream_SetPosition(s, start);
+ Stream_Write_UINT32(s, 0); /* bPad */
+ Stream_SetPosition(s, start);
+
+ if (!WTSVirtualChannelWrite(context->priv->ChannelHandle, Stream_Pointer(s), end - start,
+ &written))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
+ error = ERROR_INTERNAL_ERROR;
+ }
+
+ context->block_no = (context->block_no + 1) % 256;
+
+out:
+ Stream_SetPosition(s, 0);
+ context->priv->out_pending_frames = 0;
+ return error;
+}
+
+/**
+ * Function description
+ * context->priv->lock should be obtained before calling this function
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_server_send_wave2_pdu(RdpsndServerContext* context, UINT16 formatNo,
+ const BYTE* data, size_t size, BOOL encoded,
+ UINT16 timestamp, UINT32 audioTimeStamp)
+{
+ size_t end = 0;
+ ULONG written = 0;
+ UINT error = CHANNEL_RC_OK;
+ BOOL status = 0;
+ wStream* s = rdpsnd_server_get_buffer(context);
+
+ if (!Stream_EnsureRemainingCapacity(s, 16))
+ {
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ /* Wave2 PDU */
+ Stream_Write_UINT8(s, SNDC_WAVE2); /* msgType */
+ Stream_Write_UINT8(s, 0); /* bPad */
+ Stream_Write_UINT16(s, 0); /* BodySize */
+ Stream_Write_UINT16(s, timestamp); /* wTimeStamp */
+ Stream_Write_UINT16(s, formatNo); /* wFormatNo */
+ Stream_Write_UINT8(s, context->block_no); /* cBlockNo */
+ Stream_Write_UINT8(s, 0); /* bPad */
+ Stream_Write_UINT8(s, 0); /* bPad */
+ Stream_Write_UINT8(s, 0); /* bPad */
+ Stream_Write_UINT32(s, audioTimeStamp); /* dwAudioTimeStamp */
+
+ if (encoded)
+ {
+ if (!Stream_EnsureRemainingCapacity(s, size))
+ {
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ Stream_Write(s, data, size);
+ }
+ else
+ {
+ AUDIO_FORMAT* format = NULL;
+
+ if (!freerdp_dsp_encode(context->priv->dsp_context, context->src_format, data, size, s))
+ {
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ format = &context->client_formats[formatNo];
+ if (!rdpsnd_server_align_wave_pdu(s, format->nBlockAlign))
+ {
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+ }
+
+ end = Stream_GetPosition(s);
+ Stream_SetPosition(s, 2);
+ Stream_Write_UINT16(s, end - 4);
+
+ status = WTSVirtualChannelWrite(context->priv->ChannelHandle, (PCHAR)Stream_Buffer(s), end,
+ &written);
+
+ if (!status || (end != written))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelWrite failed! [stream length=%" PRIdz " - written=%" PRIu32,
+ end, written);
+ error = ERROR_INTERNAL_ERROR;
+ }
+
+ context->block_no = (context->block_no + 1) % 256;
+
+out:
+ Stream_SetPosition(s, 0);
+ context->priv->out_pending_frames = 0;
+ return error;
+}
+
+/* Wrapper function to send WAVE or WAVE2 PDU depending on client connected */
+static UINT rdpsnd_server_send_audio_pdu(RdpsndServerContext* context, UINT16 wTimestamp)
+{
+ const BYTE* src = NULL;
+ size_t length = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if (context->selected_client_format >= context->num_client_formats)
+ return ERROR_INTERNAL_ERROR;
+
+ src = context->priv->out_buffer;
+ length = context->priv->out_pending_frames * context->priv->src_bytes_per_frame;
+
+ if (context->clientVersion >= CHANNEL_VERSION_WIN_8)
+ return rdpsnd_server_send_wave2_pdu(context, context->selected_client_format, src, length,
+ FALSE, wTimestamp, wTimestamp);
+ else
+ return rdpsnd_server_send_wave_pdu(context, wTimestamp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_server_send_samples(RdpsndServerContext* context, const void* buf,
+ size_t nframes, UINT16 wTimestamp)
+{
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ EnterCriticalSection(&context->priv->lock);
+
+ if (context->selected_client_format >= context->num_client_formats)
+ {
+ /* It's possible while format negotiation has not been done */
+ WLog_WARN(TAG, "Drop samples because client format has not been negotiated.");
+ error = ERROR_NOT_READY;
+ goto out;
+ }
+
+ while (nframes > 0)
+ {
+ const size_t cframes =
+ MIN(nframes, context->priv->out_frames - context->priv->out_pending_frames);
+ size_t cframesize = cframes * context->priv->src_bytes_per_frame;
+ CopyMemory(context->priv->out_buffer +
+ (context->priv->out_pending_frames * context->priv->src_bytes_per_frame),
+ buf, cframesize);
+ buf = (const BYTE*)buf + cframesize;
+ nframes -= cframes;
+ context->priv->out_pending_frames += cframes;
+
+ if (context->priv->out_pending_frames >= context->priv->out_frames)
+ {
+ if ((error = rdpsnd_server_send_audio_pdu(context, wTimestamp)))
+ {
+ WLog_ERR(TAG, "rdpsnd_server_send_audio_pdu failed with error %" PRIu32 "", error);
+ break;
+ }
+ }
+ }
+
+out:
+ LeaveCriticalSection(&context->priv->lock);
+ return error;
+}
+
+/**
+ * Send encoded audio samples using a Wave2 PDU.
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_server_send_samples2(RdpsndServerContext* context, UINT16 formatNo,
+ const void* buf, size_t size, UINT16 timestamp,
+ UINT32 audioTimeStamp)
+{
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if (context->clientVersion < CHANNEL_VERSION_WIN_8)
+ return ERROR_INTERNAL_ERROR;
+
+ EnterCriticalSection(&context->priv->lock);
+
+ error =
+ rdpsnd_server_send_wave2_pdu(context, formatNo, buf, size, TRUE, timestamp, audioTimeStamp);
+
+ LeaveCriticalSection(&context->priv->lock);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_server_set_volume(RdpsndServerContext* context, UINT16 left, UINT16 right)
+{
+ size_t len = 0;
+ BOOL status = 0;
+ ULONG written = 0;
+ wStream* s = rdpsnd_server_get_buffer(context);
+
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT8(s, SNDC_SETVOLUME);
+ Stream_Write_UINT8(s, 0);
+ Stream_Write_UINT16(s, 4); /* Payload length */
+ Stream_Write_UINT16(s, left);
+ Stream_Write_UINT16(s, right);
+ len = Stream_GetPosition(s);
+
+ status = WTSVirtualChannelWrite(context->priv->ChannelHandle, (PCHAR)Stream_Buffer(s),
+ (ULONG)len, &written);
+ Stream_SetPosition(s, 0);
+ return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_server_close(RdpsndServerContext* context)
+{
+ size_t pos = 0;
+ BOOL status = 0;
+ ULONG written = 0;
+ UINT error = CHANNEL_RC_OK;
+ wStream* s = rdpsnd_server_get_buffer(context);
+
+ EnterCriticalSection(&context->priv->lock);
+
+ if (context->priv->out_pending_frames > 0)
+ {
+ if (context->selected_client_format >= context->num_client_formats)
+ {
+ WLog_ERR(TAG, "Pending audio frame exists while no format selected.");
+ error = ERROR_INVALID_DATA;
+ }
+ else if ((error = rdpsnd_server_send_audio_pdu(context, 0)))
+ {
+ WLog_ERR(TAG, "rdpsnd_server_send_audio_pdu failed with error %" PRIu32 "", error);
+ }
+ }
+
+ LeaveCriticalSection(&context->priv->lock);
+
+ if (error)
+ return error;
+
+ context->selected_client_format = 0xFFFF;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write_UINT8(s, SNDC_CLOSE);
+ Stream_Write_UINT8(s, 0);
+ Stream_Seek_UINT16(s);
+ pos = Stream_GetPosition(s);
+ Stream_SetPosition(s, 2);
+ Stream_Write_UINT16(s, pos - 4);
+ Stream_SetPosition(s, pos);
+ status = WTSVirtualChannelWrite(context->priv->ChannelHandle, (PCHAR)Stream_Buffer(s),
+ Stream_GetPosition(s), &written);
+ Stream_SetPosition(s, 0);
+ return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_server_start(RdpsndServerContext* context)
+{
+ void* buffer = NULL;
+ DWORD bytesReturned = 0;
+ RdpsndServerPrivate* priv = NULL;
+ UINT error = ERROR_INTERNAL_ERROR;
+ PULONG pSessionId = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ priv = context->priv;
+ priv->SessionId = WTS_CURRENT_SESSION;
+
+ if (context->use_dynamic_virtual_channel)
+ {
+ UINT32 channelId = 0;
+ BOOL status = TRUE;
+
+ if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId,
+ (LPSTR*)&pSessionId, &bytesReturned))
+ {
+ priv->SessionId = (DWORD)*pSessionId;
+ WTSFreeMemory(pSessionId);
+ priv->ChannelHandle = (HANDLE)WTSVirtualChannelOpenEx(
+ priv->SessionId, RDPSND_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC);
+ if (!priv->ChannelHandle)
+ {
+ WLog_ERR(TAG, "Open audio dynamic virtual channel (%s) failed!",
+ RDPSND_DVC_CHANNEL_NAME);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ channelId = WTSChannelGetIdByHandle(priv->ChannelHandle);
+
+ IFCALLRET(context->ChannelIdAssigned, status, context, channelId);
+ if (!status)
+ {
+ WLog_ERR(TAG, "context->ChannelIdAssigned failed!");
+ goto out_close;
+ }
+ }
+ else
+ {
+ WLog_ERR(TAG, "WTSQuerySessionInformationA failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+ else
+ {
+ priv->ChannelHandle =
+ WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, RDPSND_CHANNEL_NAME);
+ if (!priv->ChannelHandle)
+ {
+ WLog_ERR(TAG, "Open audio static virtual channel (rdpsnd) failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+
+ if (!WTSVirtualChannelQuery(priv->ChannelHandle, WTSVirtualEventHandle, &buffer,
+ &bytesReturned) ||
+ (bytesReturned != sizeof(HANDLE)))
+ {
+ WLog_ERR(TAG,
+ "error during WTSVirtualChannelQuery(WTSVirtualEventHandle) or invalid returned "
+ "size(%" PRIu32 ")",
+ bytesReturned);
+
+ if (buffer)
+ WTSFreeMemory(buffer);
+
+ goto out_close;
+ }
+
+ CopyMemory(&priv->channelEvent, buffer, sizeof(HANDLE));
+ WTSFreeMemory(buffer);
+ priv->rdpsnd_pdu = Stream_New(NULL, 4096);
+
+ if (!priv->rdpsnd_pdu)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out_close;
+ }
+
+ if (!InitializeCriticalSectionEx(&context->priv->lock, 0, 0))
+ {
+ WLog_ERR(TAG, "InitializeCriticalSectionEx failed!");
+ goto out_pdu;
+ }
+
+ if ((error = rdpsnd_server_send_formats(context)))
+ {
+ WLog_ERR(TAG, "rdpsnd_server_send_formats failed with error %" PRIu32 "", error);
+ goto out_lock;
+ }
+
+ if (priv->ownThread)
+ {
+ context->priv->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (!context->priv->StopEvent)
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ goto out_lock;
+ }
+
+ context->priv->Thread =
+ CreateThread(NULL, 0, rdpsnd_server_thread, (void*)context, 0, NULL);
+
+ if (!context->priv->Thread)
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ goto out_stopEvent;
+ }
+ }
+
+ return CHANNEL_RC_OK;
+out_stopEvent:
+ CloseHandle(context->priv->StopEvent);
+ context->priv->StopEvent = NULL;
+out_lock:
+ DeleteCriticalSection(&context->priv->lock);
+out_pdu:
+ Stream_Free(context->priv->rdpsnd_pdu, TRUE);
+ context->priv->rdpsnd_pdu = NULL;
+out_close:
+ WTSVirtualChannelClose(context->priv->ChannelHandle);
+ context->priv->ChannelHandle = NULL;
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpsnd_server_stop(RdpsndServerContext* context)
+{
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ if (!context->priv->StopEvent)
+ return error;
+
+ if (context->priv->ownThread)
+ {
+ if (context->priv->StopEvent)
+ {
+ SetEvent(context->priv->StopEvent);
+
+ if (WaitForSingleObject(context->priv->Thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ CloseHandle(context->priv->Thread);
+ CloseHandle(context->priv->StopEvent);
+ context->priv->Thread = NULL;
+ context->priv->StopEvent = NULL;
+ }
+ }
+
+ DeleteCriticalSection(&context->priv->lock);
+
+ if (context->priv->rdpsnd_pdu)
+ {
+ Stream_Free(context->priv->rdpsnd_pdu, TRUE);
+ context->priv->rdpsnd_pdu = NULL;
+ }
+
+ if (context->priv->ChannelHandle)
+ {
+ WTSVirtualChannelClose(context->priv->ChannelHandle);
+ context->priv->ChannelHandle = NULL;
+ }
+
+ return error;
+}
+
+RdpsndServerContext* rdpsnd_server_context_new(HANDLE vcm)
+{
+ RdpsndServerPrivate* priv = NULL;
+ RdpsndServerContext* context = (RdpsndServerContext*)calloc(1, sizeof(RdpsndServerContext));
+
+ if (!context)
+ goto fail;
+
+ context->vcm = vcm;
+ context->Start = rdpsnd_server_start;
+ context->Stop = rdpsnd_server_stop;
+ context->selected_client_format = 0xFFFF;
+ context->Initialize = rdpsnd_server_initialize;
+ context->SendFormats = rdpsnd_server_send_formats;
+ context->SelectFormat = rdpsnd_server_select_format;
+ context->Training = rdpsnd_server_training;
+ context->SendSamples = rdpsnd_server_send_samples;
+ context->SendSamples2 = rdpsnd_server_send_samples2;
+ context->SetVolume = rdpsnd_server_set_volume;
+ context->Close = rdpsnd_server_close;
+ context->priv = priv = (RdpsndServerPrivate*)calloc(1, sizeof(RdpsndServerPrivate));
+
+ if (!priv)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ goto fail;
+ }
+
+ priv->dsp_context = freerdp_dsp_context_new(TRUE);
+
+ if (!priv->dsp_context)
+ {
+ WLog_ERR(TAG, "freerdp_dsp_context_new failed!");
+ goto fail;
+ }
+
+ priv->input_stream = Stream_New(NULL, 4);
+
+ if (!priv->input_stream)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ goto fail;
+ }
+
+ priv->expectedBytes = 4;
+ priv->waitingHeader = TRUE;
+ priv->ownThread = TRUE;
+ return context;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ rdpsnd_server_context_free(context);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void rdpsnd_server_context_reset(RdpsndServerContext* context)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ context->priv->expectedBytes = 4;
+ context->priv->waitingHeader = TRUE;
+ Stream_SetPosition(context->priv->input_stream, 0);
+}
+
+void rdpsnd_server_context_free(RdpsndServerContext* context)
+{
+ if (!context)
+ return;
+
+ if (context->priv)
+ {
+ rdpsnd_server_stop(context);
+
+ free(context->priv->out_buffer);
+
+ if (context->priv->dsp_context)
+ freerdp_dsp_context_free(context->priv->dsp_context);
+
+ if (context->priv->input_stream)
+ Stream_Free(context->priv->input_stream, TRUE);
+ }
+
+ free(context->server_formats);
+ free(context->client_formats);
+ free(context->priv);
+ free(context);
+}
+
+HANDLE rdpsnd_server_get_event_handle(RdpsndServerContext* context)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ return context->priv->channelEvent;
+}
+
+/*
+ * Handle rpdsnd messages - server side
+ *
+ * @param Server side context
+ *
+ * @return 0 on success
+ * ERROR_NO_DATA if no data could be read this time
+ * otherwise error
+ */
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT rdpsnd_server_handle_messages(RdpsndServerContext* context)
+{
+ DWORD bytesReturned = 0;
+ UINT ret = CHANNEL_RC_OK;
+ RdpsndServerPrivate* priv = NULL;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+
+ priv = context->priv;
+ s = priv->input_stream;
+
+ if (!WTSVirtualChannelRead(priv->ChannelHandle, 0, Stream_Pointer(s), priv->expectedBytes,
+ &bytesReturned))
+ {
+ if (GetLastError() == ERROR_NO_DATA)
+ return ERROR_NO_DATA;
+
+ WLog_ERR(TAG, "channel connection closed");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ priv->expectedBytes -= bytesReturned;
+ Stream_Seek(s, bytesReturned);
+
+ if (priv->expectedBytes)
+ return CHANNEL_RC_OK;
+
+ Stream_SealLength(s);
+ Stream_SetPosition(s, 0);
+
+ if (priv->waitingHeader)
+ {
+ /* header case */
+ Stream_Read_UINT8(s, priv->msgType);
+ Stream_Seek_UINT8(s); /* bPad */
+ Stream_Read_UINT16(s, priv->expectedBytes);
+ priv->waitingHeader = FALSE;
+ Stream_SetPosition(s, 0);
+
+ if (priv->expectedBytes)
+ {
+ if (!Stream_EnsureCapacity(s, priv->expectedBytes))
+ {
+ WLog_ERR(TAG, "Stream_EnsureCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ return CHANNEL_RC_OK;
+ }
+ }
+
+ /* when here we have the header + the body */
+#ifdef WITH_DEBUG_SND
+ WLog_DBG(TAG, "message type %" PRIu8 "", priv->msgType);
+#endif
+ priv->expectedBytes = 4;
+ priv->waitingHeader = TRUE;
+
+ switch (priv->msgType)
+ {
+ case SNDC_WAVECONFIRM:
+ ret = rdpsnd_server_recv_waveconfirm(context, s);
+ break;
+
+ case SNDC_TRAINING:
+ ret = rdpsnd_server_recv_trainingconfirm(context, s);
+ break;
+
+ case SNDC_FORMATS:
+ ret = rdpsnd_server_recv_formats(context, s);
+
+ if ((ret == CHANNEL_RC_OK) && (context->clientVersion < CHANNEL_VERSION_WIN_7))
+ IFCALL(context->Activated, context);
+
+ break;
+
+ case SNDC_QUALITYMODE:
+ ret = rdpsnd_server_recv_quality_mode(context, s);
+
+ if ((ret == CHANNEL_RC_OK) && (context->clientVersion >= CHANNEL_VERSION_WIN_7))
+ IFCALL(context->Activated, context);
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "UNKNOWN MESSAGE TYPE!! (0x%02" PRIX8 ")", priv->msgType);
+ ret = ERROR_INVALID_DATA;
+ break;
+ }
+
+ Stream_SetPosition(s, 0);
+ return ret;
+}
diff --git a/channels/rdpsnd/server/rdpsnd_main.h b/channels/rdpsnd/server/rdpsnd_main.h
new file mode 100644
index 0000000..8623dd4
--- /dev/null
+++ b/channels/rdpsnd/server/rdpsnd_main.h
@@ -0,0 +1,59 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Server Audio Virtual Channel
+ *
+ * Copyright 2012 Vic Lee
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPSND_SERVER_MAIN_H
+#define FREERDP_CHANNEL_RDPSND_SERVER_MAIN_H
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+#include <freerdp/codec/dsp.h>
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/server/rdpsnd.h>
+
+#define TAG CHANNELS_TAG("rdpsnd.server")
+
+struct s_rdpsnd_server_private
+{
+ BOOL ownThread;
+ HANDLE Thread;
+ HANDLE StopEvent;
+ HANDLE channelEvent;
+ void* ChannelHandle;
+ DWORD SessionId;
+
+ BOOL waitingHeader;
+ DWORD expectedBytes;
+ BYTE msgType;
+ wStream* input_stream;
+ wStream* rdpsnd_pdu;
+ BYTE* out_buffer;
+ size_t out_buffer_size;
+ size_t out_frames;
+ size_t out_pending_frames;
+ UINT32 src_bytes_per_sample;
+ UINT32 src_bytes_per_frame;
+ FREERDP_DSP_CONTEXT* dsp_context;
+ CRITICAL_SECTION lock; /* Protect out_buffer and related parameters */
+};
+
+#endif /* FREERDP_CHANNEL_RDPSND_SERVER_MAIN_H */
diff --git a/channels/remdesk/CMakeLists.txt b/channels/remdesk/CMakeLists.txt
new file mode 100644
index 0000000..23f1cf7
--- /dev/null
+++ b/channels/remdesk/CMakeLists.txt
@@ -0,0 +1,26 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("remdesk")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/remdesk/ChannelOptions.cmake b/channels/remdesk/ChannelOptions.cmake
new file mode 100644
index 0000000..17518e6
--- /dev/null
+++ b/channels/remdesk/ChannelOptions.cmake
@@ -0,0 +1,12 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT ON)
+
+define_channel_options(NAME "remdesk" TYPE "static"
+ DESCRIPTION "Remote Assistance Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RA]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
diff --git a/channels/remdesk/client/CMakeLists.txt b/channels/remdesk/client/CMakeLists.txt
new file mode 100644
index 0000000..77decc9
--- /dev/null
+++ b/channels/remdesk/client/CMakeLists.txt
@@ -0,0 +1,29 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("remdesk")
+
+set(${MODULE_PREFIX}_SRCS
+ remdesk_main.c
+ remdesk_main.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr freerdp
+)
+
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx")
diff --git a/channels/remdesk/client/remdesk_main.c b/channels/remdesk/client/remdesk_main.c
new file mode 100644
index 0000000..1d39ed1
--- /dev/null
+++ b/channels/remdesk/client/remdesk_main.c
@@ -0,0 +1,1108 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Remote Assistance Virtual Channel
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/print.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/assistance.h>
+
+#include <freerdp/channels/log.h>
+#include <freerdp/client/remdesk.h>
+
+#include "remdesk_main.h"
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_virtual_channel_write(remdeskPlugin* remdesk, wStream* s)
+{
+ UINT32 status = 0;
+
+ if (!remdesk)
+ {
+ WLog_ERR(TAG, "remdesk was null!");
+ Stream_Free(s, TRUE);
+ return CHANNEL_RC_INVALID_INSTANCE;
+ }
+
+ WINPR_ASSERT(remdesk->channelEntryPoints.pVirtualChannelWriteEx);
+ status = remdesk->channelEntryPoints.pVirtualChannelWriteEx(
+ remdesk->InitHandle, remdesk->OpenHandle, Stream_Buffer(s), (UINT32)Stream_Length(s), s);
+
+ if (status != CHANNEL_RC_OK)
+ {
+ Stream_Free(s, TRUE);
+ WLog_ERR(TAG, "pVirtualChannelWriteEx failed with %s [%08" PRIX32 "]",
+ WTSErrorToString(status), status);
+ }
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_generate_expert_blob(remdeskPlugin* remdesk)
+{
+ char* name = NULL;
+ char* pass = NULL;
+ const char* password = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(remdesk);
+
+ WINPR_ASSERT(remdesk->rdpcontext);
+ settings = remdesk->rdpcontext->settings;
+ WINPR_ASSERT(settings);
+
+ if (remdesk->ExpertBlob)
+ return CHANNEL_RC_OK;
+
+ password = freerdp_settings_get_string(settings, FreeRDP_RemoteAssistancePassword);
+ if (!password)
+ password = freerdp_settings_get_string(settings, FreeRDP_Password);
+
+ if (!password)
+ {
+ WLog_ERR(TAG, "password was not set!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ name = freerdp_settings_get_string(settings, FreeRDP_Username);
+
+ if (!name)
+ name = "Expert";
+
+ const char* stub = freerdp_settings_get_string(settings, FreeRDP_RemoteAssistancePassStub);
+ remdesk->EncryptedPassStub =
+ freerdp_assistance_encrypt_pass_stub(password, stub, &(remdesk->EncryptedPassStubSize));
+
+ if (!remdesk->EncryptedPassStub)
+ {
+ WLog_ERR(TAG, "freerdp_assistance_encrypt_pass_stub failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ pass = freerdp_assistance_bin_to_hex_string(remdesk->EncryptedPassStub,
+ remdesk->EncryptedPassStubSize);
+
+ if (!pass)
+ {
+ WLog_ERR(TAG, "freerdp_assistance_bin_to_hex_string failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ remdesk->ExpertBlob = freerdp_assistance_construct_expert_blob(name, pass);
+ free(pass);
+
+ if (!remdesk->ExpertBlob)
+ {
+ WLog_ERR(TAG, "freerdp_assistance_construct_expert_blob failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_read_channel_header(wStream* s, REMDESK_CHANNEL_HEADER* header)
+{
+ UINT32 ChannelNameLen = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, ChannelNameLen); /* ChannelNameLen (4 bytes) */
+ Stream_Read_UINT32(s, header->DataLength); /* DataLen (4 bytes) */
+
+ if (ChannelNameLen > 64)
+ {
+ WLog_ERR(TAG, "ChannelNameLen > 64!");
+ return ERROR_INVALID_DATA;
+ }
+
+ if ((ChannelNameLen % 2) != 0)
+ {
+ WLog_ERR(TAG, "ChannelNameLen %% 2) != 0 ");
+ return ERROR_INVALID_DATA;
+ }
+
+ if (Stream_Read_UTF16_String_As_UTF8_Buffer(s, ChannelNameLen / sizeof(WCHAR),
+ header->ChannelName,
+ ARRAYSIZE(header->ChannelName)) < 0)
+ return ERROR_INTERNAL_ERROR;
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_write_channel_header(wStream* s, REMDESK_CHANNEL_HEADER* header)
+{
+ UINT32 ChannelNameLen = 0;
+ WCHAR ChannelNameW[32] = { 0 };
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ for (size_t index = 0; index < 32; index++)
+ {
+ ChannelNameW[index] = (WCHAR)header->ChannelName[index];
+ }
+
+ ChannelNameLen = (strnlen(header->ChannelName, sizeof(header->ChannelName)) + 1) * 2;
+ Stream_Write_UINT32(s, ChannelNameLen); /* ChannelNameLen (4 bytes) */
+ Stream_Write_UINT32(s, header->DataLength); /* DataLen (4 bytes) */
+ Stream_Write(s, ChannelNameW, ChannelNameLen); /* ChannelName (variable) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_write_ctl_header(wStream* s, REMDESK_CTL_HEADER* ctlHeader)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(ctlHeader);
+
+ remdesk_write_channel_header(s, &ctlHeader->ch);
+ Stream_Write_UINT32(s, ctlHeader->msgType); /* msgType (4 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_prepare_ctl_header(REMDESK_CTL_HEADER* ctlHeader, UINT32 msgType,
+ UINT32 msgSize)
+{
+ WINPR_ASSERT(ctlHeader);
+
+ ctlHeader->msgType = msgType;
+ sprintf_s(ctlHeader->ch.ChannelName, ARRAYSIZE(ctlHeader->ch.ChannelName),
+ REMDESK_CHANNEL_CTL_NAME);
+ ctlHeader->ch.DataLength = 4 + msgSize;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_recv_ctl_server_announce_pdu(remdeskPlugin* remdesk, wStream* s,
+ REMDESK_CHANNEL_HEADER* header)
+{
+ WINPR_ASSERT(remdesk);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_recv_ctl_version_info_pdu(remdeskPlugin* remdesk, wStream* s,
+ REMDESK_CHANNEL_HEADER* header)
+{
+ UINT32 versionMajor = 0;
+ UINT32 versionMinor = 0;
+
+ WINPR_ASSERT(remdesk);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, versionMajor); /* versionMajor (4 bytes) */
+ Stream_Read_UINT32(s, versionMinor); /* versionMinor (4 bytes) */
+
+ if ((versionMajor != 1) || (versionMinor > 2) || (versionMinor == 0))
+ {
+ WLog_ERR(TAG, "Unsupported protocol version %" PRId32 ".%" PRId32, versionMajor,
+ versionMinor);
+ }
+
+ remdesk->Version = versionMinor;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_send_ctl_version_info_pdu(remdeskPlugin* remdesk)
+{
+ wStream* s = NULL;
+ REMDESK_CTL_VERSION_INFO_PDU pdu;
+ UINT error = 0;
+
+ WINPR_ASSERT(remdesk);
+
+ remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_VERSIONINFO, 8);
+ pdu.versionMajor = 1;
+ pdu.versionMinor = 2;
+ s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.ch.DataLength);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ remdesk_write_ctl_header(s, &(pdu.ctlHeader));
+ Stream_Write_UINT32(s, pdu.versionMajor); /* versionMajor (4 bytes) */
+ Stream_Write_UINT32(s, pdu.versionMinor); /* versionMinor (4 bytes) */
+ Stream_SealLength(s);
+
+ if ((error = remdesk_virtual_channel_write(remdesk, s)))
+ WLog_ERR(TAG, "remdesk_virtual_channel_write failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_recv_ctl_result_pdu(remdeskPlugin* remdesk, wStream* s,
+ REMDESK_CHANNEL_HEADER* header, UINT32* pResult)
+{
+ UINT32 result = 0;
+
+ WINPR_ASSERT(remdesk);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+ WINPR_ASSERT(pResult);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, result); /* result (4 bytes) */
+ *pResult = result;
+ // WLog_DBG(TAG, "RemdeskRecvResult: 0x%08"PRIX32"", result);
+ switch (result)
+ {
+ case REMDESK_ERROR_HELPEESAIDNO:
+ WLog_DBG(TAG, "remote assistance connection request was denied");
+ return ERROR_CONNECTION_REFUSED;
+
+ default:
+ break;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_send_ctl_authenticate_pdu(remdeskPlugin* remdesk)
+{
+ UINT error = ERROR_INTERNAL_ERROR;
+ wStream* s = NULL;
+ size_t cbExpertBlobW = 0;
+ WCHAR* expertBlobW = NULL;
+ size_t cbRaConnectionStringW = 0;
+ WCHAR* raConnectionStringW = NULL;
+ REMDESK_CTL_AUTHENTICATE_PDU pdu = { 0 };
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(remdesk);
+
+ if ((error = remdesk_generate_expert_blob(remdesk)))
+ {
+ WLog_ERR(TAG, "remdesk_generate_expert_blob failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ pdu.expertBlob = remdesk->ExpertBlob;
+ WINPR_ASSERT(remdesk->rdpcontext);
+ settings = remdesk->rdpcontext->settings;
+ WINPR_ASSERT(settings);
+
+ pdu.raConnectionString =
+ freerdp_settings_get_string(settings, FreeRDP_RemoteAssistanceRCTicket);
+ raConnectionStringW = ConvertUtf8ToWCharAlloc(pdu.raConnectionString, &cbRaConnectionStringW);
+
+ if (!raConnectionStringW || (cbRaConnectionStringW > UINT32_MAX / sizeof(WCHAR)))
+ goto out;
+
+ cbRaConnectionStringW = cbRaConnectionStringW * sizeof(WCHAR);
+
+ expertBlobW = ConvertUtf8ToWCharAlloc(pdu.expertBlob, &cbExpertBlobW);
+
+ if (!expertBlobW || (cbExpertBlobW > UINT32_MAX / sizeof(WCHAR)))
+ goto out;
+
+ cbExpertBlobW = cbExpertBlobW * sizeof(WCHAR);
+ remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_AUTHENTICATE,
+ cbRaConnectionStringW + cbExpertBlobW);
+ s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.ch.DataLength);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ remdesk_write_ctl_header(s, &(pdu.ctlHeader));
+ Stream_Write(s, (BYTE*)raConnectionStringW, cbRaConnectionStringW);
+ Stream_Write(s, (BYTE*)expertBlobW, cbExpertBlobW);
+ Stream_SealLength(s);
+
+ if ((error = remdesk_virtual_channel_write(remdesk, s)))
+ WLog_ERR(TAG, "remdesk_virtual_channel_write failed with error %" PRIu32 "!", error);
+
+out:
+ free(raConnectionStringW);
+ free(expertBlobW);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_send_ctl_remote_control_desktop_pdu(remdeskPlugin* remdesk)
+{
+ UINT error = 0;
+ size_t length = 0;
+ wStream* s = NULL;
+ size_t cbRaConnectionStringW = 0;
+ WCHAR* raConnectionStringW = NULL;
+ REMDESK_CTL_REMOTE_CONTROL_DESKTOP_PDU pdu = { 0 };
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(remdesk);
+ WINPR_ASSERT(remdesk->rdpcontext);
+ settings = remdesk->rdpcontext->settings;
+ WINPR_ASSERT(settings);
+
+ pdu.raConnectionString =
+ freerdp_settings_get_string(settings, FreeRDP_RemoteAssistanceRCTicket);
+ raConnectionStringW = ConvertUtf8ToWCharAlloc(pdu.raConnectionString, &length);
+
+ if (!raConnectionStringW)
+ return ERROR_INTERNAL_ERROR;
+
+ cbRaConnectionStringW = length * sizeof(WCHAR);
+ remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_REMOTE_CONTROL_DESKTOP,
+ cbRaConnectionStringW);
+ s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.ch.DataLength);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ remdesk_write_ctl_header(s, &(pdu.ctlHeader));
+ Stream_Write(s, (BYTE*)raConnectionStringW, cbRaConnectionStringW);
+ Stream_SealLength(s);
+
+ if ((error = remdesk_virtual_channel_write(remdesk, s)))
+ WLog_ERR(TAG, "remdesk_virtual_channel_write failed with error %" PRIu32 "!", error);
+
+out:
+ free(raConnectionStringW);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_send_ctl_verify_password_pdu(remdeskPlugin* remdesk)
+{
+ UINT error = ERROR_INTERNAL_ERROR;
+ wStream* s = NULL;
+ size_t cbExpertBlobW = 0;
+ WCHAR* expertBlobW = NULL;
+ REMDESK_CTL_VERIFY_PASSWORD_PDU pdu = { 0 };
+
+ WINPR_ASSERT(remdesk);
+
+ if ((error = remdesk_generate_expert_blob(remdesk)))
+ {
+ WLog_ERR(TAG, "remdesk_generate_expert_blob failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ pdu.expertBlob = remdesk->ExpertBlob;
+ expertBlobW = ConvertUtf8ToWCharAlloc(pdu.expertBlob, &cbExpertBlobW);
+
+ if (!expertBlobW || (cbExpertBlobW > UINT32_MAX / sizeof(WCHAR)))
+ goto out;
+
+ cbExpertBlobW = cbExpertBlobW * sizeof(WCHAR);
+ remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_VERIFY_PASSWORD, cbExpertBlobW);
+ s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.ch.DataLength);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ remdesk_write_ctl_header(s, &(pdu.ctlHeader));
+ Stream_Write(s, (BYTE*)expertBlobW, cbExpertBlobW);
+ Stream_SealLength(s);
+
+ if ((error = remdesk_virtual_channel_write(remdesk, s)))
+ WLog_ERR(TAG, "remdesk_virtual_channel_write failed with error %" PRIu32 "!", error);
+
+out:
+ free(expertBlobW);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_send_ctl_expert_on_vista_pdu(remdeskPlugin* remdesk)
+{
+ UINT error = 0;
+ wStream* s = NULL;
+ REMDESK_CTL_EXPERT_ON_VISTA_PDU pdu;
+
+ WINPR_ASSERT(remdesk);
+
+ if ((error = remdesk_generate_expert_blob(remdesk)))
+ {
+ WLog_ERR(TAG, "remdesk_generate_expert_blob failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ pdu.EncryptedPasswordLength = remdesk->EncryptedPassStubSize;
+ pdu.EncryptedPassword = remdesk->EncryptedPassStub;
+ remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_EXPERT_ON_VISTA,
+ pdu.EncryptedPasswordLength);
+ s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.ch.DataLength);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ remdesk_write_ctl_header(s, &(pdu.ctlHeader));
+ Stream_Write(s, pdu.EncryptedPassword, pdu.EncryptedPasswordLength);
+ Stream_SealLength(s);
+ return remdesk_virtual_channel_write(remdesk, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_recv_ctl_pdu(remdeskPlugin* remdesk, wStream* s, REMDESK_CHANNEL_HEADER* header)
+{
+ UINT error = CHANNEL_RC_OK;
+ UINT32 msgType = 0;
+ UINT32 result = 0;
+
+ WINPR_ASSERT(remdesk);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, msgType); /* msgType (4 bytes) */
+
+ // WLog_DBG(TAG, "msgType: %"PRIu32"", msgType);
+
+ switch (msgType)
+ {
+ case REMDESK_CTL_REMOTE_CONTROL_DESKTOP:
+ break;
+
+ case REMDESK_CTL_RESULT:
+ if ((error = remdesk_recv_ctl_result_pdu(remdesk, s, header, &result)))
+ WLog_ERR(TAG, "remdesk_recv_ctl_result_pdu failed with error %" PRIu32 "", error);
+
+ break;
+
+ case REMDESK_CTL_AUTHENTICATE:
+ break;
+
+ case REMDESK_CTL_SERVER_ANNOUNCE:
+ if ((error = remdesk_recv_ctl_server_announce_pdu(remdesk, s, header)))
+ WLog_ERR(TAG, "remdesk_recv_ctl_server_announce_pdu failed with error %" PRIu32 "",
+ error);
+
+ break;
+
+ case REMDESK_CTL_DISCONNECT:
+ break;
+
+ case REMDESK_CTL_VERSIONINFO:
+ if ((error = remdesk_recv_ctl_version_info_pdu(remdesk, s, header)))
+ {
+ WLog_ERR(TAG, "remdesk_recv_ctl_version_info_pdu failed with error %" PRIu32 "",
+ error);
+ break;
+ }
+
+ if (remdesk->Version == 1)
+ {
+ if ((error = remdesk_send_ctl_version_info_pdu(remdesk)))
+ {
+ WLog_ERR(TAG, "remdesk_send_ctl_version_info_pdu failed with error %" PRIu32 "",
+ error);
+ break;
+ }
+
+ if ((error = remdesk_send_ctl_authenticate_pdu(remdesk)))
+ {
+ WLog_ERR(TAG, "remdesk_send_ctl_authenticate_pdu failed with error %" PRIu32 "",
+ error);
+ break;
+ }
+
+ if ((error = remdesk_send_ctl_remote_control_desktop_pdu(remdesk)))
+ {
+ WLog_ERR(
+ TAG,
+ "remdesk_send_ctl_remote_control_desktop_pdu failed with error %" PRIu32 "",
+ error);
+ break;
+ }
+ }
+ else if (remdesk->Version == 2)
+ {
+ if ((error = remdesk_send_ctl_expert_on_vista_pdu(remdesk)))
+ {
+ WLog_ERR(TAG,
+ "remdesk_send_ctl_expert_on_vista_pdu failed with error %" PRIu32 "",
+ error);
+ break;
+ }
+
+ if ((error = remdesk_send_ctl_verify_password_pdu(remdesk)))
+ {
+ WLog_ERR(TAG,
+ "remdesk_send_ctl_verify_password_pdu failed with error %" PRIu32 "",
+ error);
+ break;
+ }
+ }
+
+ break;
+
+ case REMDESK_CTL_ISCONNECTED:
+ break;
+
+ case REMDESK_CTL_VERIFY_PASSWORD:
+ break;
+
+ case REMDESK_CTL_EXPERT_ON_VISTA:
+ break;
+
+ case REMDESK_CTL_RANOVICE_NAME:
+ break;
+
+ case REMDESK_CTL_RAEXPERT_NAME:
+ break;
+
+ case REMDESK_CTL_TOKEN:
+ break;
+
+ default:
+ WLog_ERR(TAG, "unknown msgType: %" PRIu32 "", msgType);
+ error = ERROR_INVALID_DATA;
+ break;
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_process_receive(remdeskPlugin* remdesk, wStream* s)
+{
+ UINT status = 0;
+ REMDESK_CHANNEL_HEADER header;
+
+ WINPR_ASSERT(remdesk);
+ WINPR_ASSERT(s);
+
+#if 0
+ WLog_DBG(TAG, "RemdeskReceive: %"PRIuz"", Stream_GetRemainingLength(s));
+ winpr_HexDump(Stream_ConstPointer(s), Stream_GetRemainingLength(s));
+#endif
+
+ if ((status = remdesk_read_channel_header(s, &header)))
+ {
+ WLog_ERR(TAG, "remdesk_read_channel_header failed with error %" PRIu32 "", status);
+ return status;
+ }
+
+ if (strcmp(header.ChannelName, "RC_CTL") == 0)
+ {
+ status = remdesk_recv_ctl_pdu(remdesk, s, &header);
+ }
+ else if (strcmp(header.ChannelName, "70") == 0)
+ {
+ }
+ else if (strcmp(header.ChannelName, "71") == 0)
+ {
+ }
+ else if (strcmp(header.ChannelName, ".") == 0)
+ {
+ }
+ else if (strcmp(header.ChannelName, "1000.") == 0)
+ {
+ }
+ else if (strcmp(header.ChannelName, "RA_FX") == 0)
+ {
+ }
+ else
+ {
+ }
+
+ return status;
+}
+
+static void remdesk_process_connect(remdeskPlugin* remdesk)
+{
+ WINPR_ASSERT(remdesk);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_virtual_channel_event_data_received(remdeskPlugin* remdesk, const void* pData,
+ UINT32 dataLength, UINT32 totalLength,
+ UINT32 dataFlags)
+{
+ wStream* data_in = NULL;
+
+ WINPR_ASSERT(remdesk);
+
+ if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME))
+ {
+ return CHANNEL_RC_OK;
+ }
+
+ if (dataFlags & CHANNEL_FLAG_FIRST)
+ {
+ if (remdesk->data_in)
+ Stream_Free(remdesk->data_in, TRUE);
+
+ remdesk->data_in = Stream_New(NULL, totalLength);
+
+ if (!remdesk->data_in)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+
+ data_in = remdesk->data_in;
+
+ if (!Stream_EnsureRemainingCapacity(data_in, dataLength))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write(data_in, pData, dataLength);
+
+ if (dataFlags & CHANNEL_FLAG_LAST)
+ {
+ if (Stream_Capacity(data_in) != Stream_GetPosition(data_in))
+ {
+ WLog_ERR(TAG, "read error");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ remdesk->data_in = NULL;
+ Stream_SealLength(data_in);
+ Stream_SetPosition(data_in, 0);
+
+ if (!MessageQueue_Post(remdesk->queue, NULL, 0, (void*)data_in, NULL))
+ {
+ WLog_ERR(TAG, "MessageQueue_Post failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static VOID VCAPITYPE remdesk_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle,
+ UINT event, LPVOID pData,
+ UINT32 dataLength, UINT32 totalLength,
+ UINT32 dataFlags)
+{
+ UINT error = CHANNEL_RC_OK;
+ remdeskPlugin* remdesk = (remdeskPlugin*)lpUserParam;
+
+ switch (event)
+ {
+ case CHANNEL_EVENT_INITIALIZED:
+ break;
+
+ case CHANNEL_EVENT_DATA_RECEIVED:
+ if (!remdesk || (remdesk->OpenHandle != openHandle))
+ {
+ WLog_ERR(TAG, "error no match");
+ return;
+ }
+ if ((error = remdesk_virtual_channel_event_data_received(remdesk, pData, dataLength,
+ totalLength, dataFlags)))
+ WLog_ERR(TAG,
+ "remdesk_virtual_channel_event_data_received failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_WRITE_CANCELLED:
+ case CHANNEL_EVENT_WRITE_COMPLETE:
+ {
+ wStream* s = (wStream*)pData;
+ Stream_Free(s, TRUE);
+ }
+ break;
+
+ case CHANNEL_EVENT_USER:
+ break;
+
+ default:
+ WLog_ERR(TAG, "unhandled event %" PRIu32 "!", event);
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (error && remdesk && remdesk->rdpcontext)
+ setChannelError(remdesk->rdpcontext, error,
+ "remdesk_virtual_channel_open_event_ex reported an error");
+}
+
+static DWORD WINAPI remdesk_virtual_channel_client_thread(LPVOID arg)
+{
+ wStream* data = NULL;
+ wMessage message = { 0 };
+ remdeskPlugin* remdesk = (remdeskPlugin*)arg;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(remdesk);
+
+ remdesk_process_connect(remdesk);
+
+ while (1)
+ {
+ if (!MessageQueue_Wait(remdesk->queue))
+ {
+ WLog_ERR(TAG, "MessageQueue_Wait failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (!MessageQueue_Peek(remdesk->queue, &message, TRUE))
+ {
+ WLog_ERR(TAG, "MessageQueue_Peek failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (message.id == WMQ_QUIT)
+ break;
+
+ if (message.id == 0)
+ {
+ data = (wStream*)message.wParam;
+
+ if ((error = remdesk_process_receive(remdesk, data)))
+ {
+ WLog_ERR(TAG, "remdesk_process_receive failed with error %" PRIu32 "!", error);
+ Stream_Free(data, TRUE);
+ break;
+ }
+
+ Stream_Free(data, TRUE);
+ }
+ }
+
+ if (error && remdesk->rdpcontext)
+ setChannelError(remdesk->rdpcontext, error,
+ "remdesk_virtual_channel_client_thread reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_virtual_channel_event_connected(remdeskPlugin* remdesk, LPVOID pData,
+ UINT32 dataLength)
+{
+ UINT error = 0;
+
+ WINPR_ASSERT(remdesk);
+
+ remdesk->queue = MessageQueue_New(NULL);
+
+ if (!remdesk->queue)
+ {
+ WLog_ERR(TAG, "MessageQueue_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+
+ remdesk->thread =
+ CreateThread(NULL, 0, remdesk_virtual_channel_client_thread, (void*)remdesk, 0, NULL);
+
+ if (!remdesk->thread)
+ {
+ WLog_ERR(TAG, "CreateThread failed");
+ error = ERROR_INTERNAL_ERROR;
+ goto error_out;
+ }
+
+ return remdesk->channelEntryPoints.pVirtualChannelOpenEx(
+ remdesk->InitHandle, &remdesk->OpenHandle, remdesk->channelDef.name,
+ remdesk_virtual_channel_open_event_ex);
+error_out:
+ MessageQueue_Free(remdesk->queue);
+ remdesk->queue = NULL;
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_virtual_channel_event_disconnected(remdeskPlugin* remdesk)
+{
+ UINT rc = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(remdesk);
+
+ if (remdesk->queue && remdesk->thread)
+ {
+ if (MessageQueue_PostQuit(remdesk->queue, 0) &&
+ (WaitForSingleObject(remdesk->thread, INFINITE) == WAIT_FAILED))
+ {
+ rc = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", rc);
+ return rc;
+ }
+ }
+
+ if (remdesk->OpenHandle != 0)
+ {
+ WINPR_ASSERT(remdesk->channelEntryPoints.pVirtualChannelCloseEx);
+ rc = remdesk->channelEntryPoints.pVirtualChannelCloseEx(remdesk->InitHandle,
+ remdesk->OpenHandle);
+
+ if (CHANNEL_RC_OK != rc)
+ {
+ WLog_ERR(TAG, "pVirtualChannelCloseEx failed with %s [%08" PRIX32 "]",
+ WTSErrorToString(rc), rc);
+ }
+
+ remdesk->OpenHandle = 0;
+ }
+ MessageQueue_Free(remdesk->queue);
+ CloseHandle(remdesk->thread);
+ Stream_Free(remdesk->data_in, TRUE);
+ remdesk->data_in = NULL;
+ remdesk->queue = NULL;
+ remdesk->thread = NULL;
+ return rc;
+}
+
+static void remdesk_virtual_channel_event_terminated(remdeskPlugin* remdesk)
+{
+ WINPR_ASSERT(remdesk);
+
+ remdesk->InitHandle = 0;
+ free(remdesk->context);
+ free(remdesk);
+}
+
+static VOID VCAPITYPE remdesk_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle,
+ UINT event, LPVOID pData,
+ UINT dataLength)
+{
+ UINT error = CHANNEL_RC_OK;
+ remdeskPlugin* remdesk = (remdeskPlugin*)lpUserParam;
+
+ if (!remdesk || (remdesk->InitHandle != pInitHandle))
+ {
+ WLog_ERR(TAG, "error no match");
+ return;
+ }
+
+ switch (event)
+ {
+ case CHANNEL_EVENT_CONNECTED:
+ if ((error = remdesk_virtual_channel_event_connected(remdesk, pData, dataLength)))
+ WLog_ERR(TAG,
+ "remdesk_virtual_channel_event_connected failed with error %" PRIu32 "",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_DISCONNECTED:
+ if ((error = remdesk_virtual_channel_event_disconnected(remdesk)))
+ WLog_ERR(TAG,
+ "remdesk_virtual_channel_event_disconnected failed with error %" PRIu32 "",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_TERMINATED:
+ remdesk_virtual_channel_event_terminated(remdesk);
+ break;
+
+ case CHANNEL_EVENT_ATTACHED:
+ case CHANNEL_EVENT_DETACHED:
+ default:
+ break;
+ }
+
+ if (error && remdesk->rdpcontext)
+ setChannelError(remdesk->rdpcontext, error,
+ "remdesk_virtual_channel_init_event reported an error");
+}
+
+/* remdesk is always built-in */
+#define VirtualChannelEntryEx remdesk_VirtualChannelEntryEx
+
+FREERDP_ENTRY_POINT(BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS pEntryPoints,
+ PVOID pInitHandle))
+{
+ UINT rc = 0;
+ remdeskPlugin* remdesk = NULL;
+ RemdeskClientContext* context = NULL;
+ CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx = NULL;
+
+ if (!pEntryPoints)
+ {
+ return FALSE;
+ }
+
+ remdesk = (remdeskPlugin*)calloc(1, sizeof(remdeskPlugin));
+
+ if (!remdesk)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return FALSE;
+ }
+
+ remdesk->channelDef.options = CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP |
+ CHANNEL_OPTION_COMPRESS_RDP | CHANNEL_OPTION_SHOW_PROTOCOL;
+ sprintf_s(remdesk->channelDef.name, ARRAYSIZE(remdesk->channelDef.name),
+ REMDESK_SVC_CHANNEL_NAME);
+ remdesk->Version = 2;
+ pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints;
+
+ if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) &&
+ (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER))
+ {
+ context = (RemdeskClientContext*)calloc(1, sizeof(RemdeskClientContext));
+
+ if (!context)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ goto error_out;
+ }
+
+ context->handle = (void*)remdesk;
+ remdesk->context = context;
+ remdesk->rdpcontext = pEntryPointsEx->context;
+ }
+
+ CopyMemory(&(remdesk->channelEntryPoints), pEntryPoints,
+ sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX));
+ remdesk->InitHandle = pInitHandle;
+ rc = remdesk->channelEntryPoints.pVirtualChannelInitEx(
+ remdesk, context, pInitHandle, &remdesk->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000,
+ remdesk_virtual_channel_init_event_ex);
+
+ if (CHANNEL_RC_OK != rc)
+ {
+ WLog_ERR(TAG, "pVirtualChannelInitEx failed with %s [%08" PRIX32 "]", WTSErrorToString(rc),
+ rc);
+ goto error_out;
+ }
+
+ remdesk->channelEntryPoints.pInterface = context;
+ return TRUE;
+error_out:
+ free(remdesk);
+ free(context);
+ return FALSE;
+}
diff --git a/channels/remdesk/client/remdesk_main.h b/channels/remdesk/client/remdesk_main.h
new file mode 100644
index 0000000..0d9c48d
--- /dev/null
+++ b/channels/remdesk/client/remdesk_main.h
@@ -0,0 +1,61 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Remote Assistance Virtual Channel
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_REMDESK_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_REMDESK_CLIENT_MAIN_H
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+
+#include <freerdp/api.h>
+#include <freerdp/svc.h>
+#include <freerdp/addin.h>
+#include <freerdp/settings.h>
+
+#include <freerdp/client/remdesk.h>
+
+#include <freerdp/channels/log.h>
+#define TAG CHANNELS_TAG("remdesk.client")
+
+typedef struct
+{
+ CHANNEL_DEF channelDef;
+ CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints;
+
+ RemdeskClientContext* context;
+
+ HANDLE thread;
+ wStream* data_in;
+ void* InitHandle;
+ DWORD OpenHandle;
+ wMessageQueue* queue;
+
+ UINT32 Version;
+ char* ExpertBlob;
+ BYTE* EncryptedPassStub;
+ size_t EncryptedPassStubSize;
+ rdpContext* rdpcontext;
+} remdeskPlugin;
+
+#endif /* FREERDP_CHANNEL_REMDESK_CLIENT_MAIN_H */
diff --git a/channels/remdesk/server/CMakeLists.txt b/channels/remdesk/server/CMakeLists.txt
new file mode 100644
index 0000000..f8a395e
--- /dev/null
+++ b/channels/remdesk/server/CMakeLists.txt
@@ -0,0 +1,29 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_server("remdesk")
+
+set(${MODULE_PREFIX}_SRCS
+ remdesk_main.c
+ remdesk_main.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+)
+
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry")
diff --git a/channels/remdesk/server/remdesk_main.c b/channels/remdesk/server/remdesk_main.c
new file mode 100644
index 0000000..4c823aa
--- /dev/null
+++ b/channels/remdesk/server/remdesk_main.c
@@ -0,0 +1,736 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Remote Assistance Virtual Channel
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+
+#include <freerdp/freerdp.h>
+
+#include "remdesk_main.h"
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_virtual_channel_write(RemdeskServerContext* context, wStream* s)
+{
+ BOOL status = 0;
+ ULONG BytesWritten = 0;
+ status = WTSVirtualChannelWrite(context->priv->ChannelHandle, (PCHAR)Stream_Buffer(s),
+ Stream_Length(s), &BytesWritten);
+ return (status) ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_read_channel_header(wStream* s, REMDESK_CHANNEL_HEADER* header)
+{
+ UINT32 ChannelNameLen = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Read_UINT32(s, ChannelNameLen); /* ChannelNameLen (4 bytes) */
+ Stream_Read_UINT32(s, header->DataLength); /* DataLen (4 bytes) */
+
+ if (ChannelNameLen > 64)
+ {
+ WLog_ERR(TAG, "ChannelNameLen > 64!");
+ return ERROR_INVALID_DATA;
+ }
+
+ if ((ChannelNameLen % 2) != 0)
+ {
+ WLog_ERR(TAG, "(ChannelNameLen %% 2) != 0!");
+ return ERROR_INVALID_DATA;
+ }
+
+ if (Stream_Read_UTF16_String_As_UTF8_Buffer(s, ChannelNameLen / sizeof(WCHAR),
+ header->ChannelName,
+ ARRAYSIZE(header->ChannelName)) < 0)
+ return ERROR_INVALID_DATA;
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_write_channel_header(wStream* s, REMDESK_CHANNEL_HEADER* header)
+{
+ UINT32 ChannelNameLen = 0;
+ WCHAR ChannelNameW[32] = { 0 };
+
+ for (size_t index = 0; index < 32; index++)
+ {
+ ChannelNameW[index] = (WCHAR)header->ChannelName[index];
+ }
+
+ ChannelNameLen = (strnlen(header->ChannelName, sizeof(header->ChannelName)) + 1) * 2;
+ Stream_Write_UINT32(s, ChannelNameLen); /* ChannelNameLen (4 bytes) */
+ Stream_Write_UINT32(s, header->DataLength); /* DataLen (4 bytes) */
+ Stream_Write(s, ChannelNameW, ChannelNameLen); /* ChannelName (variable) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_write_ctl_header(wStream* s, REMDESK_CTL_HEADER* ctlHeader)
+{
+ UINT error = 0;
+
+ if ((error = remdesk_write_channel_header(s, (REMDESK_CHANNEL_HEADER*)ctlHeader)))
+ {
+ WLog_ERR(TAG, "remdesk_write_channel_header failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ Stream_Write_UINT32(s, ctlHeader->msgType); /* msgType (4 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_prepare_ctl_header(REMDESK_CTL_HEADER* ctlHeader, UINT32 msgType,
+ UINT32 msgSize)
+{
+ ctlHeader->msgType = msgType;
+ sprintf_s(ctlHeader->ch.ChannelName, ARRAYSIZE(ctlHeader->ch.ChannelName),
+ REMDESK_CHANNEL_CTL_NAME);
+ ctlHeader->ch.DataLength = 4 + msgSize;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_send_ctl_result_pdu(RemdeskServerContext* context, UINT32 result)
+{
+ wStream* s = NULL;
+ REMDESK_CTL_RESULT_PDU pdu;
+ UINT error = 0;
+ pdu.result = result;
+
+ if ((error = remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_RESULT, 4)))
+ {
+ WLog_ERR(TAG, "remdesk_prepare_ctl_header failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.ch.DataLength);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if ((error = remdesk_write_ctl_header(s, &(pdu.ctlHeader))))
+ {
+ WLog_ERR(TAG, "remdesk_write_ctl_header failed with error %" PRIu32 "!", error);
+ goto out;
+ }
+
+ Stream_Write_UINT32(s, pdu.result); /* result (4 bytes) */
+ Stream_SealLength(s);
+
+ if ((error = remdesk_virtual_channel_write(context, s)))
+ WLog_ERR(TAG, "remdesk_virtual_channel_write failed with error %" PRIu32 "!", error);
+
+out:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_send_ctl_version_info_pdu(RemdeskServerContext* context)
+{
+ wStream* s = NULL;
+ REMDESK_CTL_VERSION_INFO_PDU pdu;
+ UINT error = 0;
+
+ if ((error = remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_VERSIONINFO, 8)))
+ {
+ WLog_ERR(TAG, "remdesk_prepare_ctl_header failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ pdu.versionMajor = 1;
+ pdu.versionMinor = 2;
+ s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.ch.DataLength);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if ((error = remdesk_write_ctl_header(s, &(pdu.ctlHeader))))
+ {
+ WLog_ERR(TAG, "remdesk_write_ctl_header failed with error %" PRIu32 "!", error);
+ goto out;
+ }
+
+ Stream_Write_UINT32(s, pdu.versionMajor); /* versionMajor (4 bytes) */
+ Stream_Write_UINT32(s, pdu.versionMinor); /* versionMinor (4 bytes) */
+ Stream_SealLength(s);
+
+ if ((error = remdesk_virtual_channel_write(context, s)))
+ WLog_ERR(TAG, "remdesk_virtual_channel_write failed with error %" PRIu32 "!", error);
+
+out:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_recv_ctl_version_info_pdu(RemdeskServerContext* context, wStream* s,
+ REMDESK_CHANNEL_HEADER* header)
+{
+ UINT32 versionMajor = 0;
+ UINT32 versionMinor = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, versionMajor); /* versionMajor (4 bytes) */
+ Stream_Read_UINT32(s, versionMinor); /* versionMinor (4 bytes) */
+ if ((versionMajor != 1) || (versionMinor != 2))
+ {
+ WLog_ERR(TAG, "REMOTEDESKTOP_CTL_VERSIONINFO_PACKET invalid version %" PRIu32 ".%" PRIu32,
+ versionMajor, versionMinor);
+ return ERROR_INVALID_DATA;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_recv_ctl_remote_control_desktop_pdu(RemdeskServerContext* context, wStream* s,
+ REMDESK_CHANNEL_HEADER* header)
+{
+ SSIZE_T cchStringW = 0;
+ SSIZE_T cbRaConnectionStringW = 0;
+ REMDESK_CTL_REMOTE_CONTROL_DESKTOP_PDU pdu = { 0 };
+ UINT error = 0;
+ UINT32 msgLength = header->DataLength - 4;
+ const WCHAR* pStringW = Stream_ConstPointer(s);
+ const WCHAR* raConnectionStringW = pStringW;
+
+ while ((msgLength > 0) && pStringW[cchStringW])
+ {
+ msgLength -= 2;
+ cchStringW++;
+ }
+
+ if (pStringW[cchStringW] || !cchStringW)
+ return ERROR_INVALID_DATA;
+
+ cchStringW++;
+ cbRaConnectionStringW = cchStringW * 2;
+ pdu.raConnectionString =
+ ConvertWCharNToUtf8Alloc(raConnectionStringW, cbRaConnectionStringW / sizeof(WCHAR), NULL);
+ if (!pdu.raConnectionString)
+ return ERROR_INTERNAL_ERROR;
+
+ WLog_INFO(TAG, "RaConnectionString: %s", pdu.raConnectionString);
+ free(pdu.raConnectionString);
+
+ if ((error = remdesk_send_ctl_result_pdu(context, 0)))
+ WLog_ERR(TAG, "remdesk_send_ctl_result_pdu failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_recv_ctl_authenticate_pdu(RemdeskServerContext* context, wStream* s,
+ REMDESK_CHANNEL_HEADER* header)
+{
+ int cchStringW = 0;
+ UINT32 msgLength = 0;
+ int cbExpertBlobW = 0;
+ const WCHAR* expertBlobW = NULL;
+ int cbRaConnectionStringW = 0;
+ REMDESK_CTL_AUTHENTICATE_PDU pdu = { 0 };
+ msgLength = header->DataLength - 4;
+ const WCHAR* pStringW = Stream_ConstPointer(s);
+ const WCHAR* raConnectionStringW = pStringW;
+
+ while ((msgLength > 0) && pStringW[cchStringW])
+ {
+ msgLength -= 2;
+ cchStringW++;
+ }
+
+ if (pStringW[cchStringW] || !cchStringW)
+ return ERROR_INVALID_DATA;
+
+ cchStringW++;
+ cbRaConnectionStringW = cchStringW * 2;
+ pStringW += cchStringW;
+ expertBlobW = pStringW;
+ cchStringW = 0;
+
+ while ((msgLength > 0) && pStringW[cchStringW])
+ {
+ msgLength -= 2;
+ cchStringW++;
+ }
+
+ if (pStringW[cchStringW] || !cchStringW)
+ return ERROR_INVALID_DATA;
+
+ cchStringW++;
+ cbExpertBlobW = cchStringW * 2;
+ pdu.raConnectionString =
+ ConvertWCharNToUtf8Alloc(raConnectionStringW, cbRaConnectionStringW / sizeof(WCHAR), NULL);
+ if (!pdu.raConnectionString)
+ return ERROR_INTERNAL_ERROR;
+
+ pdu.expertBlob = ConvertWCharNToUtf8Alloc(expertBlobW, cbExpertBlobW / sizeof(WCHAR), NULL);
+ if (!pdu.expertBlob)
+ {
+ free(pdu.raConnectionString);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_INFO(TAG, "RaConnectionString: %s ExpertBlob: %s", pdu.raConnectionString, pdu.expertBlob);
+ free(pdu.raConnectionString);
+ free(pdu.expertBlob);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_recv_ctl_verify_password_pdu(RemdeskServerContext* context, wStream* s,
+ REMDESK_CHANNEL_HEADER* header)
+{
+ SSIZE_T cbExpertBlobW = 0;
+ REMDESK_CTL_VERIFY_PASSWORD_PDU pdu;
+ UINT error = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ const WCHAR* expertBlobW = Stream_ConstPointer(s);
+ cbExpertBlobW = header->DataLength - 4;
+
+ pdu.expertBlob = ConvertWCharNToUtf8Alloc(expertBlobW, cbExpertBlobW / sizeof(WCHAR), NULL);
+ if (pdu.expertBlob)
+ return ERROR_INTERNAL_ERROR;
+
+ WLog_INFO(TAG, "ExpertBlob: %s", pdu.expertBlob);
+
+ if ((error = remdesk_send_ctl_result_pdu(context, 0)))
+ WLog_ERR(TAG, "remdesk_send_ctl_result_pdu failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_recv_ctl_pdu(RemdeskServerContext* context, wStream* s,
+ REMDESK_CHANNEL_HEADER* header)
+{
+ UINT error = CHANNEL_RC_OK;
+ UINT32 msgType = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, msgType); /* msgType (4 bytes) */
+ WLog_INFO(TAG, "msgType: %" PRIu32 "", msgType);
+
+ switch (msgType)
+ {
+ case REMDESK_CTL_REMOTE_CONTROL_DESKTOP:
+ if ((error = remdesk_recv_ctl_remote_control_desktop_pdu(context, s, header)))
+ {
+ WLog_ERR(TAG,
+ "remdesk_recv_ctl_remote_control_desktop_pdu failed with error %" PRIu32
+ "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ case REMDESK_CTL_AUTHENTICATE:
+ if ((error = remdesk_recv_ctl_authenticate_pdu(context, s, header)))
+ {
+ WLog_ERR(TAG, "remdesk_recv_ctl_authenticate_pdu failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ case REMDESK_CTL_DISCONNECT:
+ break;
+
+ case REMDESK_CTL_VERSIONINFO:
+ if ((error = remdesk_recv_ctl_version_info_pdu(context, s, header)))
+ {
+ WLog_ERR(TAG, "remdesk_recv_ctl_version_info_pdu failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ case REMDESK_CTL_ISCONNECTED:
+ break;
+
+ case REMDESK_CTL_VERIFY_PASSWORD:
+ if ((error = remdesk_recv_ctl_verify_password_pdu(context, s, header)))
+ {
+ WLog_ERR(TAG, "remdesk_recv_ctl_verify_password_pdu failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ case REMDESK_CTL_EXPERT_ON_VISTA:
+ break;
+
+ case REMDESK_CTL_RANOVICE_NAME:
+ break;
+
+ case REMDESK_CTL_RAEXPERT_NAME:
+ break;
+
+ case REMDESK_CTL_TOKEN:
+ break;
+
+ default:
+ WLog_ERR(TAG, "remdesk_recv_control_pdu: unknown msgType: %" PRIu32 "", msgType);
+ error = ERROR_INVALID_DATA;
+ break;
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_server_receive_pdu(RemdeskServerContext* context, wStream* s)
+{
+ UINT error = CHANNEL_RC_OK;
+ REMDESK_CHANNEL_HEADER header;
+#if 0
+ WLog_INFO(TAG, "RemdeskReceive: %"PRIuz"", Stream_GetRemainingLength(s));
+ winpr_HexDump(WCHAR* expertBlobW = NULL;(s), Stream_GetRemainingLength(s));
+#endif
+
+ if ((error = remdesk_read_channel_header(s, &header)))
+ {
+ WLog_ERR(TAG, "remdesk_read_channel_header failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ if (strcmp(header.ChannelName, "RC_CTL") == 0)
+ {
+ if ((error = remdesk_recv_ctl_pdu(context, s, &header)))
+ {
+ WLog_ERR(TAG, "remdesk_recv_ctl_pdu failed with error %" PRIu32 "!", error);
+ return error;
+ }
+ }
+ else if (strcmp(header.ChannelName, "70") == 0)
+ {
+ }
+ else if (strcmp(header.ChannelName, "71") == 0)
+ {
+ }
+ else if (strcmp(header.ChannelName, ".") == 0)
+ {
+ }
+ else if (strcmp(header.ChannelName, "1000.") == 0)
+ {
+ }
+ else if (strcmp(header.ChannelName, "RA_FX") == 0)
+ {
+ }
+ else
+ {
+ }
+
+ return error;
+}
+
+static DWORD WINAPI remdesk_server_thread(LPVOID arg)
+{
+ wStream* s = NULL;
+ DWORD status = 0;
+ DWORD nCount = 0;
+ void* buffer = NULL;
+ UINT32* pHeader = NULL;
+ UINT32 PduLength = 0;
+ HANDLE events[8];
+ HANDLE ChannelEvent = NULL;
+ DWORD BytesReturned = 0;
+ RemdeskServerContext* context = NULL;
+ UINT error = 0;
+ context = (RemdeskServerContext*)arg;
+ buffer = NULL;
+ BytesReturned = 0;
+ ChannelEvent = NULL;
+ s = Stream_New(NULL, 4096);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ if (WTSVirtualChannelQuery(context->priv->ChannelHandle, WTSVirtualEventHandle, &buffer,
+ &BytesReturned) == TRUE)
+ {
+ if (BytesReturned == sizeof(HANDLE))
+ CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE));
+
+ WTSFreeMemory(buffer);
+ }
+ else
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelQuery failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ nCount = 0;
+ events[nCount++] = ChannelEvent;
+ events[nCount++] = context->priv->StopEvent;
+
+ if ((error = remdesk_send_ctl_version_info_pdu(context)))
+ {
+ WLog_ERR(TAG, "remdesk_send_ctl_version_info_pdu failed with error %" PRIu32 "!", error);
+ goto out;
+ }
+
+ while (1)
+ {
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error);
+ break;
+ }
+
+ status = WaitForSingleObject(context->priv->StopEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ break;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ {
+ break;
+ }
+
+ if (WTSVirtualChannelRead(context->priv->ChannelHandle, 0, (PCHAR)Stream_Buffer(s),
+ Stream_Capacity(s), &BytesReturned))
+ {
+ if (BytesReturned)
+ Stream_Seek(s, BytesReturned);
+ }
+ else
+ {
+ if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ break;
+ }
+ }
+
+ if (Stream_GetPosition(s) >= 8)
+ {
+ pHeader = (UINT32*)Stream_Buffer(s);
+ PduLength = pHeader[0] + pHeader[1] + 8;
+
+ if (PduLength >= Stream_GetPosition(s))
+ {
+ Stream_SealLength(s);
+ Stream_SetPosition(s, 0);
+
+ if ((error = remdesk_server_receive_pdu(context, s)))
+ {
+ WLog_ERR(TAG, "remdesk_server_receive_pdu failed with error %" PRIu32 "!",
+ error);
+ break;
+ }
+
+ Stream_SetPosition(s, 0);
+ }
+ }
+ }
+
+ Stream_Free(s, TRUE);
+out:
+
+ if (error && context->rdpcontext)
+ setChannelError(context->rdpcontext, error, "remdesk_server_thread reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_server_start(RemdeskServerContext* context)
+{
+ context->priv->ChannelHandle =
+ WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, REMDESK_SVC_CHANNEL_NAME);
+
+ if (!context->priv->ChannelHandle)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelOpen failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!(context->priv->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!(context->priv->Thread =
+ CreateThread(NULL, 0, remdesk_server_thread, (void*)context, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ CloseHandle(context->priv->StopEvent);
+ context->priv->StopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT remdesk_server_stop(RemdeskServerContext* context)
+{
+ UINT error = 0;
+ SetEvent(context->priv->StopEvent);
+
+ if (WaitForSingleObject(context->priv->Thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ CloseHandle(context->priv->Thread);
+ CloseHandle(context->priv->StopEvent);
+ return CHANNEL_RC_OK;
+}
+
+RemdeskServerContext* remdesk_server_context_new(HANDLE vcm)
+{
+ RemdeskServerContext* context = NULL;
+ context = (RemdeskServerContext*)calloc(1, sizeof(RemdeskServerContext));
+
+ if (context)
+ {
+ context->vcm = vcm;
+ context->Start = remdesk_server_start;
+ context->Stop = remdesk_server_stop;
+ context->priv = (RemdeskServerPrivate*)calloc(1, sizeof(RemdeskServerPrivate));
+
+ if (!context->priv)
+ {
+ free(context);
+ return NULL;
+ }
+
+ context->priv->Version = 1;
+ }
+
+ return context;
+}
+
+void remdesk_server_context_free(RemdeskServerContext* context)
+{
+ if (context)
+ {
+ if (context->priv->ChannelHandle != INVALID_HANDLE_VALUE)
+ WTSVirtualChannelClose(context->priv->ChannelHandle);
+
+ free(context->priv);
+ free(context);
+ }
+}
diff --git a/channels/remdesk/server/remdesk_main.h b/channels/remdesk/server/remdesk_main.h
new file mode 100644
index 0000000..4268e4e
--- /dev/null
+++ b/channels/remdesk/server/remdesk_main.h
@@ -0,0 +1,41 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Remote Assistance Virtual Channel
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_REMDESK_SERVER_MAIN_H
+#define FREERDP_CHANNEL_REMDESK_SERVER_MAIN_H
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+#include <freerdp/server/remdesk.h>
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("remdesk.server")
+
+struct s_remdesk_server_private
+{
+ HANDLE Thread;
+ HANDLE StopEvent;
+ void* ChannelHandle;
+
+ UINT32 Version;
+};
+
+#endif /* FREERDP_CHANNEL_REMDESK_SERVER_MAIN_H */
diff --git a/channels/serial/CMakeLists.txt b/channels/serial/CMakeLists.txt
new file mode 100644
index 0000000..aa5cd85
--- /dev/null
+++ b/channels/serial/CMakeLists.txt
@@ -0,0 +1,23 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("serial")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
+
diff --git a/channels/serial/ChannelOptions.cmake b/channels/serial/ChannelOptions.cmake
new file mode 100644
index 0000000..add3443
--- /dev/null
+++ b/channels/serial/ChannelOptions.cmake
@@ -0,0 +1,23 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT OFF)
+
+if(WIN32)
+ set(OPTION_CLIENT_DEFAULT OFF)
+ set(OPTION_SERVER_DEFAULT OFF)
+endif()
+
+if(ANDROID)
+ set(OPTION_CLIENT_DEFAULT OFF)
+ set(OPTION_SERVER_DEFAULT OFF)
+endif()
+
+define_channel_options(NAME "serial" TYPE "device"
+ DESCRIPTION "Serial Port Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPESP]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/serial/client/CMakeLists.txt b/channels/serial/client/CMakeLists.txt
new file mode 100644
index 0000000..ad7e379
--- /dev/null
+++ b/channels/serial/client/CMakeLists.txt
@@ -0,0 +1,27 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("serial")
+
+set(${MODULE_PREFIX}_SRCS
+ serial_main.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr freerdp
+)
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DeviceServiceEntry")
diff --git a/channels/serial/client/serial_main.c b/channels/serial/client/serial_main.c
new file mode 100644
index 0000000..22f799a
--- /dev/null
+++ b/channels/serial/client/serial_main.c
@@ -0,0 +1,978 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Serial Port Device Service Virtual Channel
+ *
+ * Copyright 2011 O.S. Systems Software Ltda.
+ * Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/collections.h>
+#include <winpr/comm.h>
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/wlog.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/utils/rdpdr_utils.h>
+
+#define TAG CHANNELS_TAG("serial.client")
+
+/* TODO: all #ifdef __linux__ could be removed once only some generic
+ * functions will be used. Replace CommReadFile by ReadFile,
+ * CommWriteFile by WriteFile etc.. */
+#if defined __linux__ && !defined ANDROID
+
+#define MAX_IRP_THREADS 5
+
+typedef struct
+{
+ DEVICE device;
+ BOOL permissive;
+ SERIAL_DRIVER_ID ServerSerialDriverId;
+ HANDLE* hComm;
+
+ wLog* log;
+ HANDLE MainThread;
+ wMessageQueue* MainIrpQueue;
+
+ /* one thread per pending IRP and indexed according their CompletionId */
+ wListDictionary* IrpThreads;
+ UINT32 IrpThreadToBeTerminatedCount;
+ CRITICAL_SECTION TerminatingIrpThreadsLock;
+ rdpContext* rdpcontext;
+} SERIAL_DEVICE;
+
+typedef struct
+{
+ SERIAL_DEVICE* serial;
+ IRP* irp;
+} IRP_THREAD_DATA;
+
+static UINT32 _GetLastErrorToIoStatus(SERIAL_DEVICE* serial)
+{
+ /* http://msdn.microsoft.com/en-us/library/ff547466%28v=vs.85%29.aspx#generic_status_values_for_serial_device_control_requests
+ */
+ switch (GetLastError())
+ {
+ case ERROR_BAD_DEVICE:
+ return STATUS_INVALID_DEVICE_REQUEST;
+
+ case ERROR_CALL_NOT_IMPLEMENTED:
+ return STATUS_NOT_IMPLEMENTED;
+
+ case ERROR_CANCELLED:
+ return STATUS_CANCELLED;
+
+ case ERROR_INSUFFICIENT_BUFFER:
+ return STATUS_BUFFER_TOO_SMALL; /* NB: STATUS_BUFFER_SIZE_TOO_SMALL not defined */
+
+ case ERROR_INVALID_DEVICE_OBJECT_PARAMETER: /* eg: SerCx2.sys' _purge() */
+ return STATUS_INVALID_DEVICE_STATE;
+
+ case ERROR_INVALID_HANDLE:
+ return STATUS_INVALID_DEVICE_REQUEST;
+
+ case ERROR_INVALID_PARAMETER:
+ return STATUS_INVALID_PARAMETER;
+
+ case ERROR_IO_DEVICE:
+ return STATUS_IO_DEVICE_ERROR;
+
+ case ERROR_IO_PENDING:
+ return STATUS_PENDING;
+
+ case ERROR_NOT_SUPPORTED:
+ return STATUS_NOT_SUPPORTED;
+
+ case ERROR_TIMEOUT:
+ return STATUS_TIMEOUT;
+ /* no default */
+ }
+
+ WLog_Print(serial->log, WLOG_DEBUG, "unexpected last-error: 0x%08" PRIX32 "", GetLastError());
+ return STATUS_UNSUCCESSFUL;
+}
+
+static UINT serial_process_irp_create(SERIAL_DEVICE* serial, IRP* irp)
+{
+ DWORD DesiredAccess = 0;
+ DWORD SharedAccess = 0;
+ DWORD CreateDisposition = 0;
+ UINT32 PathLength = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, DesiredAccess); /* DesiredAccess (4 bytes) */
+ Stream_Seek_UINT64(irp->input); /* AllocationSize (8 bytes) */
+ Stream_Seek_UINT32(irp->input); /* FileAttributes (4 bytes) */
+ Stream_Read_UINT32(irp->input, SharedAccess); /* SharedAccess (4 bytes) */
+ Stream_Read_UINT32(irp->input, CreateDisposition); /* CreateDisposition (4 bytes) */
+ Stream_Seek_UINT32(irp->input); /* CreateOptions (4 bytes) */
+ Stream_Read_UINT32(irp->input, PathLength); /* PathLength (4 bytes) */
+
+ if (!Stream_SafeSeek(irp->input, PathLength)) /* Path (variable) */
+ return ERROR_INVALID_DATA;
+
+ WINPR_ASSERT(PathLength == 0); /* MS-RDPESP 2.2.2.2 */
+#ifndef _WIN32
+ /* Windows 2012 server sends on a first call :
+ * DesiredAccess = 0x00100080: SYNCHRONIZE | FILE_READ_ATTRIBUTES
+ * SharedAccess = 0x00000007: FILE_SHARE_DELETE | FILE_SHARE_WRITE | FILE_SHARE_READ
+ * CreateDisposition = 0x00000001: CREATE_NEW
+ *
+ * then Windows 2012 sends :
+ * DesiredAccess = 0x00120089: SYNCHRONIZE | READ_CONTROL | FILE_READ_ATTRIBUTES |
+ * FILE_READ_EA | FILE_READ_DATA SharedAccess = 0x00000007: FILE_SHARE_DELETE |
+ * FILE_SHARE_WRITE | FILE_SHARE_READ CreateDisposition = 0x00000001: CREATE_NEW
+ *
+ * WINPR_ASSERT(DesiredAccess == (GENERIC_READ | GENERIC_WRITE));
+ * WINPR_ASSERT(SharedAccess == 0);
+ * WINPR_ASSERT(CreateDisposition == OPEN_EXISTING);
+ *
+ */
+ WLog_Print(serial->log, WLOG_DEBUG,
+ "DesiredAccess: 0x%" PRIX32 ", SharedAccess: 0x%" PRIX32
+ ", CreateDisposition: 0x%" PRIX32 "",
+ DesiredAccess, SharedAccess, CreateDisposition);
+ /* FIXME: As of today only the flags below are supported by CommCreateFileA: */
+ DesiredAccess = GENERIC_READ | GENERIC_WRITE;
+ SharedAccess = 0;
+ CreateDisposition = OPEN_EXISTING;
+#endif
+ serial->hComm =
+ CreateFile(serial->device.name, DesiredAccess, SharedAccess, NULL, /* SecurityAttributes */
+ CreateDisposition, 0, /* FlagsAndAttributes */
+ NULL); /* TemplateFile */
+
+ if (!serial->hComm || (serial->hComm == INVALID_HANDLE_VALUE))
+ {
+ WLog_Print(serial->log, WLOG_WARN, "CreateFile failure: %s last-error: 0x%08" PRIX32 "",
+ serial->device.name, GetLastError());
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ goto error_handle;
+ }
+
+ _comm_setServerSerialDriver(serial->hComm, serial->ServerSerialDriverId);
+ _comm_set_permissive(serial->hComm, serial->permissive);
+ /* NOTE: binary mode/raw mode required for the redirection. On
+ * Linux, CommCreateFileA forces this setting.
+ */
+ /* ZeroMemory(&dcb, sizeof(DCB)); */
+ /* dcb.DCBlength = sizeof(DCB); */
+ /* GetCommState(serial->hComm, &dcb); */
+ /* dcb.fBinary = TRUE; */
+ /* SetCommState(serial->hComm, &dcb); */
+ WINPR_ASSERT(irp->FileId == 0);
+ irp->FileId = irp->devman->id_sequence++; /* FIXME: why not ((WINPR_COMM*)hComm)->fd? */
+ irp->IoStatus = STATUS_SUCCESS;
+ WLog_Print(serial->log, WLOG_DEBUG, "%s (DeviceId: %" PRIu32 ", FileId: %" PRIu32 ") created.",
+ serial->device.name, irp->device->id, irp->FileId);
+error_handle:
+ Stream_Write_UINT32(irp->output, irp->FileId); /* FileId (4 bytes) */
+ Stream_Write_UINT8(irp->output, 0); /* Information (1 byte) */
+ return CHANNEL_RC_OK;
+}
+
+static UINT serial_process_irp_close(SERIAL_DEVICE* serial, IRP* irp)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Seek(irp->input, 32); /* Padding (32 bytes) */
+
+ if (!CloseHandle(serial->hComm))
+ {
+ WLog_Print(serial->log, WLOG_WARN, "CloseHandle failure: %s (%" PRIu32 ") closed.",
+ serial->device.name, irp->device->id);
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ goto error_handle;
+ }
+
+ WLog_Print(serial->log, WLOG_DEBUG, "%s (DeviceId: %" PRIu32 ", FileId: %" PRIu32 ") closed.",
+ serial->device.name, irp->device->id, irp->FileId);
+ serial->hComm = NULL;
+ irp->IoStatus = STATUS_SUCCESS;
+error_handle:
+ Stream_Zero(irp->output, 5); /* Padding (5 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT serial_process_irp_read(SERIAL_DEVICE* serial, IRP* irp)
+{
+ UINT32 Length = 0;
+ UINT64 Offset = 0;
+ BYTE* buffer = NULL;
+ DWORD nbRead = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, Length); /* Length (4 bytes) */
+ Stream_Read_UINT64(irp->input, Offset); /* Offset (8 bytes) */
+ Stream_Seek(irp->input, 20); /* Padding (20 bytes) */
+ buffer = (BYTE*)calloc(Length, sizeof(BYTE));
+
+ if (buffer == NULL)
+ {
+ irp->IoStatus = STATUS_NO_MEMORY;
+ goto error_handle;
+ }
+
+ /* MS-RDPESP 3.2.5.1.4: If the Offset field is not set to 0, the value MUST be ignored
+ * WINPR_ASSERT(Offset == 0);
+ */
+ WLog_Print(serial->log, WLOG_DEBUG, "reading %" PRIu32 " bytes from %s", Length,
+ serial->device.name);
+
+ /* FIXME: CommReadFile to be replaced by ReadFile */
+ if (CommReadFile(serial->hComm, buffer, Length, &nbRead, NULL))
+ {
+ irp->IoStatus = STATUS_SUCCESS;
+ }
+ else
+ {
+ WLog_Print(serial->log, WLOG_DEBUG,
+ "read failure to %s, nbRead=%" PRIu32 ", last-error: 0x%08" PRIX32 "",
+ serial->device.name, nbRead, GetLastError());
+ irp->IoStatus = _GetLastErrorToIoStatus(serial);
+ }
+
+ WLog_Print(serial->log, WLOG_DEBUG, "%" PRIu32 " bytes read from %s", nbRead,
+ serial->device.name);
+error_handle:
+ Stream_Write_UINT32(irp->output, nbRead); /* Length (4 bytes) */
+
+ if (nbRead > 0)
+ {
+ if (!Stream_EnsureRemainingCapacity(irp->output, nbRead))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ free(buffer);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write(irp->output, buffer, nbRead); /* ReadData */
+ }
+
+ free(buffer);
+ return CHANNEL_RC_OK;
+}
+
+static UINT serial_process_irp_write(SERIAL_DEVICE* serial, IRP* irp)
+{
+ UINT32 Length = 0;
+ UINT64 Offset = 0;
+ DWORD nbWritten = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, Length); /* Length (4 bytes) */
+ Stream_Read_UINT64(irp->input, Offset); /* Offset (8 bytes) */
+ if (!Stream_SafeSeek(irp->input, 20)) /* Padding (20 bytes) */
+ return ERROR_INVALID_DATA;
+
+ /* MS-RDPESP 3.2.5.1.5: The Offset field is ignored
+ * WINPR_ASSERT(Offset == 0);
+ *
+ * Using a serial printer, noticed though this field could be
+ * set.
+ */
+ WLog_Print(serial->log, WLOG_DEBUG, "writing %" PRIu32 " bytes to %s", Length,
+ serial->device.name);
+
+ const void* ptr = Stream_ConstPointer(irp->input);
+ if (!Stream_SafeSeek(irp->input, Length))
+ return ERROR_INVALID_DATA;
+ /* FIXME: CommWriteFile to be replaced by WriteFile */
+ if (CommWriteFile(serial->hComm, ptr, Length, &nbWritten, NULL))
+ {
+ irp->IoStatus = STATUS_SUCCESS;
+ }
+ else
+ {
+ WLog_Print(serial->log, WLOG_DEBUG,
+ "write failure to %s, nbWritten=%" PRIu32 ", last-error: 0x%08" PRIX32 "",
+ serial->device.name, nbWritten, GetLastError());
+ irp->IoStatus = _GetLastErrorToIoStatus(serial);
+ }
+
+ WLog_Print(serial->log, WLOG_DEBUG, "%" PRIu32 " bytes written to %s", nbWritten,
+ serial->device.name);
+ Stream_Write_UINT32(irp->output, nbWritten); /* Length (4 bytes) */
+ Stream_Write_UINT8(irp->output, 0); /* Padding (1 byte) */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT serial_process_irp_device_control(SERIAL_DEVICE* serial, IRP* irp)
+{
+ UINT32 IoControlCode = 0;
+ UINT32 InputBufferLength = 0;
+ BYTE* InputBuffer = NULL;
+ UINT32 OutputBufferLength = 0;
+ BYTE* OutputBuffer = NULL;
+ DWORD BytesReturned = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, OutputBufferLength); /* OutputBufferLength (4 bytes) */
+ Stream_Read_UINT32(irp->input, InputBufferLength); /* InputBufferLength (4 bytes) */
+ Stream_Read_UINT32(irp->input, IoControlCode); /* IoControlCode (4 bytes) */
+ Stream_Seek(irp->input, 20); /* Padding (20 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, InputBufferLength))
+ return ERROR_INVALID_DATA;
+
+ OutputBuffer = (BYTE*)calloc(OutputBufferLength, sizeof(BYTE));
+
+ if (OutputBuffer == NULL)
+ {
+ irp->IoStatus = STATUS_NO_MEMORY;
+ goto error_handle;
+ }
+
+ InputBuffer = (BYTE*)calloc(InputBufferLength, sizeof(BYTE));
+
+ if (InputBuffer == NULL)
+ {
+ irp->IoStatus = STATUS_NO_MEMORY;
+ goto error_handle;
+ }
+
+ Stream_Read(irp->input, InputBuffer, InputBufferLength);
+ WLog_Print(serial->log, WLOG_DEBUG,
+ "CommDeviceIoControl: CompletionId=%" PRIu32 ", IoControlCode=[0x%" PRIX32 "] %s",
+ irp->CompletionId, IoControlCode, _comm_serial_ioctl_name(IoControlCode));
+
+ /* FIXME: CommDeviceIoControl to be replaced by DeviceIoControl() */
+ if (CommDeviceIoControl(serial->hComm, IoControlCode, InputBuffer, InputBufferLength,
+ OutputBuffer, OutputBufferLength, &BytesReturned, NULL))
+ {
+ /* WLog_Print(serial->log, WLOG_DEBUG, "CommDeviceIoControl: CompletionId=%"PRIu32",
+ * IoControlCode=[0x%"PRIX32"] %s done", irp->CompletionId, IoControlCode,
+ * _comm_serial_ioctl_name(IoControlCode)); */
+ irp->IoStatus = STATUS_SUCCESS;
+ }
+ else
+ {
+ WLog_Print(serial->log, WLOG_DEBUG,
+ "CommDeviceIoControl failure: IoControlCode=[0x%" PRIX32
+ "] %s, last-error: 0x%08" PRIX32 "",
+ IoControlCode, _comm_serial_ioctl_name(IoControlCode), GetLastError());
+ irp->IoStatus = _GetLastErrorToIoStatus(serial);
+ }
+
+error_handle:
+ /* FIXME: find out whether it's required or not to get
+ * BytesReturned == OutputBufferLength when
+ * CommDeviceIoControl returns FALSE */
+ WINPR_ASSERT(OutputBufferLength == BytesReturned);
+ Stream_Write_UINT32(irp->output, BytesReturned); /* OutputBufferLength (4 bytes) */
+
+ if (BytesReturned > 0)
+ {
+ if (!Stream_EnsureRemainingCapacity(irp->output, BytesReturned))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ free(InputBuffer);
+ free(OutputBuffer);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write(irp->output, OutputBuffer, BytesReturned); /* OutputBuffer */
+ }
+
+ /* FIXME: Why at least Windows 2008R2 gets lost with this
+ * extra byte and likely on a IOCTL_SERIAL_SET_BAUD_RATE? The
+ * extra byte is well required according MS-RDPEFS
+ * 2.2.1.5.5 */
+ /* else */
+ /* { */
+ /* Stream_Write_UINT8(irp->output, 0); /\* Padding (1 byte) *\/ */
+ /* } */
+ free(InputBuffer);
+ free(OutputBuffer);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT serial_process_irp(SERIAL_DEVICE* serial, IRP* irp)
+{
+ UINT error = CHANNEL_RC_OK;
+ WLog_Print(serial->log, WLOG_DEBUG, "IRP MajorFunction: s, MinorFunction: 0x%08" PRIX32 "\n",
+ rdpdr_irp_string(irp->MajorFunction), irp->MinorFunction);
+
+ switch (irp->MajorFunction)
+ {
+ case IRP_MJ_CREATE:
+ error = serial_process_irp_create(serial, irp);
+ break;
+
+ case IRP_MJ_CLOSE:
+ error = serial_process_irp_close(serial, irp);
+ break;
+
+ case IRP_MJ_READ:
+ if ((error = serial_process_irp_read(serial, irp)))
+ WLog_ERR(TAG, "serial_process_irp_read failed with error %" PRIu32 "!", error);
+
+ break;
+
+ case IRP_MJ_WRITE:
+ error = serial_process_irp_write(serial, irp);
+ break;
+
+ case IRP_MJ_DEVICE_CONTROL:
+ if ((error = serial_process_irp_device_control(serial, irp)))
+ WLog_ERR(TAG, "serial_process_irp_device_control failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ default:
+ irp->IoStatus = STATUS_NOT_SUPPORTED;
+ break;
+ }
+
+ return error;
+}
+
+static DWORD WINAPI irp_thread_func(LPVOID arg)
+{
+ IRP_THREAD_DATA* data = (IRP_THREAD_DATA*)arg;
+ UINT error = 0;
+
+ /* blocks until the end of the request */
+ if ((error = serial_process_irp(data->serial, data->irp)))
+ {
+ WLog_ERR(TAG, "serial_process_irp failed with error %" PRIu32 "", error);
+ goto error_out;
+ }
+
+ EnterCriticalSection(&data->serial->TerminatingIrpThreadsLock);
+ data->serial->IrpThreadToBeTerminatedCount++;
+ error = data->irp->Complete(data->irp);
+ LeaveCriticalSection(&data->serial->TerminatingIrpThreadsLock);
+error_out:
+
+ if (error && data->serial->rdpcontext)
+ setChannelError(data->serial->rdpcontext, error, "irp_thread_func reported an error");
+
+ /* NB: At this point, the server might already being reusing
+ * the CompletionId whereas the thread is not yet
+ * terminated */
+ free(data);
+ ExitThread(error);
+ return error;
+}
+
+static void create_irp_thread(SERIAL_DEVICE* serial, IRP* irp)
+{
+ IRP_THREAD_DATA* data = NULL;
+ HANDLE irpThread = NULL;
+ HANDLE previousIrpThread = NULL;
+ uintptr_t key = 0;
+ /* for a test/debug purpose, uncomment the code below to get a
+ * single thread for all IRPs. NB: two IRPs could not be
+ * processed at the same time, typically two concurent
+ * Read/Write operations could block each other. */
+ /* serial_process_irp(serial, irp); */
+ /* irp->Complete(irp); */
+ /* return; */
+ /* NOTE: for good or bad, this implementation relies on the
+ * server to avoid a flooding of requests. see also _purge().
+ */
+ EnterCriticalSection(&serial->TerminatingIrpThreadsLock);
+
+ while (serial->IrpThreadToBeTerminatedCount > 0)
+ {
+ /* Cleaning up termitating and pending irp
+ * threads. See also: irp_thread_func() */
+ HANDLE cirpThread = NULL;
+ ULONG_PTR* ids = NULL;
+ const size_t nbIds = ListDictionary_GetKeys(serial->IrpThreads, &ids);
+
+ for (size_t i = 0; i < nbIds; i++)
+ {
+ /* Checking if ids[i] is terminating or pending */
+ DWORD waitResult = 0;
+ ULONG_PTR id = ids[i];
+ cirpThread = ListDictionary_GetItemValue(serial->IrpThreads, (void*)id);
+ /* FIXME: not quite sure a zero timeout is a good thing to check whether a thread is
+ * stil alived or not */
+ waitResult = WaitForSingleObject(cirpThread, 0);
+
+ if (waitResult == WAIT_OBJECT_0)
+ {
+ /* terminating thread */
+ /* WLog_Print(serial->log, WLOG_DEBUG, "IRP thread with CompletionId=%"PRIuz"
+ * naturally died", id); */
+ CloseHandle(cirpThread);
+ ListDictionary_Remove(serial->IrpThreads, (void*)id);
+ serial->IrpThreadToBeTerminatedCount--;
+ }
+ else if (waitResult != WAIT_TIMEOUT)
+ {
+ /* unexpected thread state */
+ WLog_Print(serial->log, WLOG_WARN,
+ "WaitForSingleObject, got an unexpected result=0x%" PRIX32 "\n",
+ waitResult);
+ WINPR_ASSERT(FALSE);
+ }
+
+ /* pending thread (but not yet terminating thread) if waitResult == WAIT_TIMEOUT */
+ }
+
+ if (serial->IrpThreadToBeTerminatedCount > 0)
+ {
+ WLog_Print(serial->log, WLOG_DEBUG, "%" PRIu32 " IRP thread(s) not yet terminated",
+ serial->IrpThreadToBeTerminatedCount);
+ Sleep(1); /* 1 ms */
+ }
+
+ free(ids);
+ }
+
+ LeaveCriticalSection(&serial->TerminatingIrpThreadsLock);
+ /* NB: At this point and thanks to the synchronization we're
+ * sure that the incoming IRP uses well a recycled
+ * CompletionId or the server sent again an IRP already posted
+ * which didn't get yet a response (this later server behavior
+ * at least observed with IOCTL_SERIAL_WAIT_ON_MASK and
+ * mstsc.exe).
+ *
+ * FIXME: behavior documented somewhere? behavior not yet
+ * observed with FreeRDP).
+ */
+ key = irp->CompletionId + 1ull;
+ previousIrpThread = ListDictionary_GetItemValue(serial->IrpThreads, (void*)key);
+
+ if (previousIrpThread)
+ {
+ /* Thread still alived <=> Request still pending */
+ WLog_Print(serial->log, WLOG_DEBUG,
+ "IRP recall: IRP with the CompletionId=%" PRIu32 " not yet completed!",
+ irp->CompletionId);
+ WINPR_ASSERT(FALSE); /* unimplemented */
+ /* TODO: WINPR_ASSERTs that previousIrpThread handles well
+ * the same request by checking more details. Need an
+ * access to the IRP object used by previousIrpThread
+ */
+ /* TODO: taking over the pending IRP or sending a kind
+ * of wake up signal to accelerate the pending
+ * request
+ *
+ * To be considered:
+ * if (IoControlCode == IOCTL_SERIAL_WAIT_ON_MASK) {
+ * pComm->PendingEvents |= SERIAL_EV_FREERDP_*;
+ * }
+ */
+ irp->Discard(irp);
+ return;
+ }
+
+ if (ListDictionary_Count(serial->IrpThreads) >= MAX_IRP_THREADS)
+ {
+ WLog_Print(serial->log, WLOG_WARN,
+ "Number of IRP threads threshold reached: %" PRIuz ", keep on anyway",
+ ListDictionary_Count(serial->IrpThreads));
+ WINPR_ASSERT(FALSE); /* unimplemented */
+ /* TODO: MAX_IRP_THREADS has been thought to avoid a
+ * flooding of pending requests. Use
+ * WaitForMultipleObjects() when available in winpr
+ * for threads.
+ */
+ }
+
+ /* error_handle to be used ... */
+ data = (IRP_THREAD_DATA*)calloc(1, sizeof(IRP_THREAD_DATA));
+
+ if (data == NULL)
+ {
+ WLog_Print(serial->log, WLOG_WARN, "Could not allocate a new IRP_THREAD_DATA.");
+ goto error_handle;
+ }
+
+ data->serial = serial;
+ data->irp = irp;
+ /* data freed by irp_thread_func */
+ irpThread = CreateThread(NULL, 0, irp_thread_func, (void*)data, 0, NULL);
+
+ if (irpThread == INVALID_HANDLE_VALUE)
+ {
+ WLog_Print(serial->log, WLOG_WARN, "Could not allocate a new IRP thread.");
+ goto error_handle;
+ }
+
+ key = irp->CompletionId + 1ull;
+
+ if (!ListDictionary_Add(serial->IrpThreads, (void*)key, irpThread))
+ {
+ WLog_ERR(TAG, "ListDictionary_Add failed!");
+ goto error_handle;
+ }
+
+ return;
+error_handle:
+ irp->IoStatus = STATUS_NO_MEMORY;
+ irp->Complete(irp);
+ free(data);
+}
+
+static void terminate_pending_irp_threads(SERIAL_DEVICE* serial)
+{
+ WINPR_ASSERT(serial);
+
+ ULONG_PTR* ids = NULL;
+ const size_t nbIds = ListDictionary_GetKeys(serial->IrpThreads, &ids);
+ WLog_Print(serial->log, WLOG_DEBUG, "Terminating %" PRIuz " IRP thread(s)", nbIds);
+
+ for (size_t i = 0; i < nbIds; i++)
+ {
+ HANDLE irpThread = NULL;
+ ULONG_PTR id = ids[i];
+ irpThread = ListDictionary_GetItemValue(serial->IrpThreads, (void*)id);
+ TerminateThread(irpThread, 0);
+ if (WaitForSingleObject(irpThread, INFINITE) == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForSingleObject failed!");
+ continue;
+ }
+
+ CloseHandle(irpThread);
+ WLog_Print(serial->log, WLOG_DEBUG, "IRP thread terminated, CompletionId %p", (void*)id);
+ }
+
+ ListDictionary_Clear(serial->IrpThreads);
+ free(ids);
+}
+
+static DWORD WINAPI serial_thread_func(LPVOID arg)
+{
+ IRP* irp = NULL;
+ wMessage message = { 0 };
+ SERIAL_DEVICE* serial = (SERIAL_DEVICE*)arg;
+ UINT error = CHANNEL_RC_OK;
+
+ while (1)
+ {
+ if (!MessageQueue_Wait(serial->MainIrpQueue))
+ {
+ WLog_ERR(TAG, "MessageQueue_Wait failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (!MessageQueue_Peek(serial->MainIrpQueue, &message, TRUE))
+ {
+ WLog_ERR(TAG, "MessageQueue_Peek failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (message.id == WMQ_QUIT)
+ {
+ terminate_pending_irp_threads(serial);
+ break;
+ }
+
+ irp = (IRP*)message.wParam;
+
+ if (irp)
+ create_irp_thread(serial, irp);
+ }
+
+ if (error && serial->rdpcontext)
+ setChannelError(serial->rdpcontext, error, "serial_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT serial_irp_request(DEVICE* device, IRP* irp)
+{
+ SERIAL_DEVICE* serial = (SERIAL_DEVICE*)device;
+ WINPR_ASSERT(irp != NULL);
+
+ if (irp == NULL)
+ return CHANNEL_RC_OK;
+
+ /* NB: ENABLE_ASYNCIO is set, (MS-RDPEFS 2.2.2.7.2) this
+ * allows the server to send multiple simultaneous read or
+ * write requests.
+ */
+
+ if (!MessageQueue_Post(serial->MainIrpQueue, NULL, 0, (void*)irp, NULL))
+ {
+ WLog_ERR(TAG, "MessageQueue_Post failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT serial_free(DEVICE* device)
+{
+ UINT error = 0;
+ SERIAL_DEVICE* serial = (SERIAL_DEVICE*)device;
+ WLog_Print(serial->log, WLOG_DEBUG, "freeing");
+ MessageQueue_PostQuit(serial->MainIrpQueue, 0);
+
+ if (WaitForSingleObject(serial->MainThread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ CloseHandle(serial->MainThread);
+
+ if (serial->hComm)
+ CloseHandle(serial->hComm);
+
+ /* Clean up resources */
+ Stream_Free(serial->device.data, TRUE);
+ MessageQueue_Free(serial->MainIrpQueue);
+ ListDictionary_Free(serial->IrpThreads);
+ DeleteCriticalSection(&serial->TerminatingIrpThreadsLock);
+ free(serial);
+ return CHANNEL_RC_OK;
+}
+
+#endif /* __linux__ */
+
+static void serial_message_free(void* obj)
+{
+ wMessage* msg = obj;
+ if (!msg)
+ return;
+ if (msg->id != 0)
+ return;
+
+ IRP* irp = (IRP*)msg->wParam;
+ if (!irp)
+ return;
+ WINPR_ASSERT(irp->Discard);
+ irp->Discard(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT serial_DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints))
+{
+ char* name = NULL;
+ char* path = NULL;
+ char* driver = NULL;
+ RDPDR_SERIAL* device = NULL;
+#if defined __linux__ && !defined ANDROID
+ size_t len = 0;
+ SERIAL_DEVICE* serial = NULL;
+#endif /* __linux__ */
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(pEntryPoints);
+
+ device = (RDPDR_SERIAL*)pEntryPoints->device;
+ WINPR_ASSERT(device);
+
+ name = device->device.Name;
+ path = device->Path;
+ driver = device->Driver;
+
+ if (!name || (name[0] == '*'))
+ {
+ /* TODO: implement auto detection of serial ports */
+ return CHANNEL_RC_OK;
+ }
+
+ if ((name && name[0]) && (path && path[0]))
+ {
+ wLog* log = NULL;
+ log = WLog_Get("com.freerdp.channel.serial.client");
+ WLog_Print(log, WLOG_DEBUG, "initializing");
+#ifndef __linux__ /* to be removed */
+ WLog_Print(log, WLOG_WARN, "Serial ports redirection not supported on this platform.");
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+#else /* __linux __ */
+ WLog_Print(log, WLOG_DEBUG, "Defining %s as %s", name, path);
+
+ if (!DefineCommDevice(name /* eg: COM1 */, path /* eg: /dev/ttyS0 */))
+ {
+ DWORD status = GetLastError();
+ WLog_ERR(TAG, "DefineCommDevice failed with %08" PRIx32, status);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ serial = (SERIAL_DEVICE*)calloc(1, sizeof(SERIAL_DEVICE));
+
+ if (!serial)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ serial->log = log;
+ serial->device.type = RDPDR_DTYP_SERIAL;
+ serial->device.name = name;
+ serial->device.IRPRequest = serial_irp_request;
+ serial->device.Free = serial_free;
+ serial->rdpcontext = pEntryPoints->rdpcontext;
+ len = strlen(name);
+ serial->device.data = Stream_New(NULL, len + 1);
+
+ if (!serial->device.data)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+
+ for (size_t i = 0; i <= len; i++)
+ Stream_Write_UINT8(serial->device.data, name[i] < 0 ? '_' : name[i]);
+
+ if (driver != NULL)
+ {
+ if (_stricmp(driver, "Serial") == 0)
+ serial->ServerSerialDriverId = SerialDriverSerialSys;
+ else if (_stricmp(driver, "SerCx") == 0)
+ serial->ServerSerialDriverId = SerialDriverSerCxSys;
+ else if (_stricmp(driver, "SerCx2") == 0)
+ serial->ServerSerialDriverId = SerialDriverSerCx2Sys;
+ else
+ {
+ WINPR_ASSERT(FALSE);
+ WLog_Print(serial->log, WLOG_DEBUG,
+ "Unknown server's serial driver: %s. SerCx2 will be used", driver);
+ serial->ServerSerialDriverId = SerialDriverSerialSys;
+ }
+ }
+ else
+ {
+ /* default driver */
+ serial->ServerSerialDriverId = SerialDriverSerialSys;
+ }
+
+ if (device->Permissive != NULL)
+ {
+ if (_stricmp(device->Permissive, "permissive") == 0)
+ {
+ serial->permissive = TRUE;
+ }
+ else
+ {
+ WLog_Print(serial->log, WLOG_DEBUG, "Unknown flag: %s", device->Permissive);
+ WINPR_ASSERT(FALSE);
+ }
+ }
+
+ WLog_Print(serial->log, WLOG_DEBUG, "Server's serial driver: %s (id: %d)", driver,
+ serial->ServerSerialDriverId);
+ /* TODO: implement auto detection of the server's serial driver */
+ serial->MainIrpQueue = MessageQueue_New(NULL);
+
+ if (!serial->MainIrpQueue)
+ {
+ WLog_ERR(TAG, "MessageQueue_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+
+ wObject* obj = MessageQueue_Object(serial->MainIrpQueue);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = serial_message_free;
+
+ /* IrpThreads content only modified by create_irp_thread() */
+ serial->IrpThreads = ListDictionary_New(FALSE);
+
+ if (!serial->IrpThreads)
+ {
+ WLog_ERR(TAG, "ListDictionary_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+
+ serial->IrpThreadToBeTerminatedCount = 0;
+ InitializeCriticalSection(&serial->TerminatingIrpThreadsLock);
+
+ if ((error = pEntryPoints->RegisterDevice(pEntryPoints->devman, (DEVICE*)serial)))
+ {
+ WLog_ERR(TAG, "EntryPoints->RegisterDevice failed with error %" PRIu32 "!", error);
+ goto error_out;
+ }
+
+ if (!(serial->MainThread =
+ CreateThread(NULL, 0, serial_thread_func, (void*)serial, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto error_out;
+ }
+
+#endif /* __linux __ */
+ }
+
+ return error;
+error_out:
+#ifdef __linux__ /* to be removed */
+ ListDictionary_Free(serial->IrpThreads);
+ MessageQueue_Free(serial->MainIrpQueue);
+ Stream_Free(serial->device.data, TRUE);
+ free(serial);
+#endif /* __linux __ */
+ return error;
+}
diff --git a/channels/server/CMakeLists.txt b/channels/server/CMakeLists.txt
new file mode 100644
index 0000000..1f49c3b
--- /dev/null
+++ b/channels/server/CMakeLists.txt
@@ -0,0 +1,43 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set(MODULE_NAME "freerdp-channels-server")
+set(MODULE_PREFIX "FREERDP_CHANNELS_SERVER")
+
+set(${MODULE_PREFIX}_SRCS
+ ${CMAKE_CURRENT_SOURCE_DIR}/channels.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/channels.h)
+
+foreach(STATIC_MODULE ${CHANNEL_STATIC_SERVER_MODULES})
+ set(STATIC_MODULE_NAME ${${STATIC_MODULE}_SERVER_NAME})
+ set(STATIC_MODULE_CHANNEL ${${STATIC_MODULE}_SERVER_CHANNEL})
+ set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${STATIC_MODULE_NAME})
+endforeach()
+
+add_library(${MODULE_NAME} STATIC ${${MODULE_PREFIX}_SRCS})
+
+if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION})
+endif()
+
+
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr freerdp)
+
+set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} PARENT_SCOPE)
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} PARENT_SCOPE)
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Common")
diff --git a/channels/server/channels.c b/channels/server/channels.c
new file mode 100644
index 0000000..35a85e8
--- /dev/null
+++ b/channels/server/channels.c
@@ -0,0 +1,172 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Server Channels
+ *
+ * Copyright 2011-2012 Vic Lee
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <freerdp/constants.h>
+#include <freerdp/server/channels.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/stream.h>
+
+#include "channels.h"
+
+/**
+ * this is a workaround to force importing symbols
+ * will need to fix that later on cleanly
+ */
+
+#if defined(CHANNEL_AUDIN_SERVER)
+#include <freerdp/server/audin.h>
+#endif
+#include <freerdp/server/rdpsnd.h>
+#include <freerdp/server/cliprdr.h>
+#include <freerdp/server/echo.h>
+#include <freerdp/server/rdpdr.h>
+#include <freerdp/server/rdpei.h>
+#include <freerdp/server/drdynvc.h>
+#include <freerdp/server/remdesk.h>
+#include <freerdp/server/encomsp.h>
+#include <freerdp/server/rail.h>
+#include <freerdp/server/telemetry.h>
+#include <freerdp/server/rdpgfx.h>
+#include <freerdp/server/disp.h>
+
+#if defined(CHANNEL_RDPEMSC_SERVER)
+#include <freerdp/server/rdpemsc.h>
+#endif /* CHANNEL_RDPEMSC_SERVER */
+
+#if defined(CHANNEL_RDPECAM_SERVER)
+#include <freerdp/server/rdpecam-enumerator.h>
+#include <freerdp/server/rdpecam.h>
+#endif
+
+#if defined(CHANNEL_LOCATION_SERVER)
+#include <freerdp/server/location.h>
+#endif /* CHANNEL_LOCATION_SERVER */
+
+#ifdef WITH_CHANNEL_GFXREDIR
+#include <freerdp/server/gfxredir.h>
+#endif /* WITH_CHANNEL_GFXREDIR */
+
+#if defined(CHANNEL_AINPUT_SERVER)
+#include <freerdp/server/ainput.h>
+#endif
+
+extern void freerdp_channels_dummy(void);
+
+void freerdp_channels_dummy(void)
+{
+#if defined(CHANNEL_AUDIN_SERVER)
+ audin_server_context* audin = NULL;
+#endif
+ RdpsndServerContext* rdpsnd = NULL;
+ CliprdrServerContext* cliprdr = NULL;
+ echo_server_context* echo = NULL;
+ RdpdrServerContext* rdpdr = NULL;
+ DrdynvcServerContext* drdynvc = NULL;
+ RdpeiServerContext* rdpei = NULL;
+ RemdeskServerContext* remdesk = NULL;
+ EncomspServerContext* encomsp = NULL;
+ RailServerContext* rail = NULL;
+ TelemetryServerContext* telemetry = NULL;
+ RdpgfxServerContext* rdpgfx = NULL;
+ DispServerContext* disp = NULL;
+#if defined(CHANNEL_RDPEMSC_SERVER)
+ MouseCursorServerContext* mouse_cursor = NULL;
+#endif /* CHANNEL_RDPEMSC_SERVER */
+#if defined(CHANNEL_RDPECAM_SERVER)
+ CamDevEnumServerContext* camera_enumerator = NULL;
+ CameraDeviceServerContext* camera_device = NULL;
+#endif
+#if defined(CHANNEL_LOCATION_SERVER)
+ LocationServerContext* location = NULL;
+#endif /* CHANNEL_LOCATION_SERVER */
+#ifdef WITH_CHANNEL_GFXREDIR
+ GfxRedirServerContext* gfxredir;
+#endif // WITH_CHANNEL_GFXREDIR
+#if defined(CHANNEL_AUDIN_SERVER)
+ audin = audin_server_context_new(NULL);
+#endif
+#if defined(CHANNEL_AUDIN_SERVER)
+ audin_server_context_free(audin);
+#endif
+ rdpsnd = rdpsnd_server_context_new(NULL);
+ rdpsnd_server_context_free(rdpsnd);
+ cliprdr = cliprdr_server_context_new(NULL);
+ cliprdr_server_context_free(cliprdr);
+ echo = echo_server_context_new(NULL);
+ echo_server_context_free(echo);
+ rdpdr = rdpdr_server_context_new(NULL);
+ rdpdr_server_context_free(rdpdr);
+ drdynvc = drdynvc_server_context_new(NULL);
+ drdynvc_server_context_free(drdynvc);
+ rdpei = rdpei_server_context_new(NULL);
+ rdpei_server_context_free(rdpei);
+ remdesk = remdesk_server_context_new(NULL);
+ remdesk_server_context_free(remdesk);
+ encomsp = encomsp_server_context_new(NULL);
+ encomsp_server_context_free(encomsp);
+ rail = rail_server_context_new(NULL);
+ rail_server_context_free(rail);
+ telemetry = telemetry_server_context_new(NULL);
+ telemetry_server_context_free(telemetry);
+ rdpgfx = rdpgfx_server_context_new(NULL);
+ rdpgfx_server_context_free(rdpgfx);
+ disp = disp_server_context_new(NULL);
+ disp_server_context_free(disp);
+
+#if defined(CHANNEL_RDPEMSC_SERVER)
+ mouse_cursor = mouse_cursor_server_context_new(NULL);
+ mouse_cursor_server_context_free(mouse_cursor);
+#endif /* CHANNEL_RDPEMSC_SERVER */
+
+#if defined(CHANNEL_RDPECAM_SERVER)
+ camera_enumerator = cam_dev_enum_server_context_new(NULL);
+ cam_dev_enum_server_context_free(camera_enumerator);
+ camera_device = camera_device_server_context_new(NULL);
+ camera_device_server_context_free(camera_device);
+#endif
+
+#if defined(CHANNEL_LOCATION_SERVER)
+ location = location_server_context_new(NULL);
+ location_server_context_free(location);
+#endif /* CHANNEL_LOCATION_SERVER */
+
+#ifdef WITH_CHANNEL_GFXREDIR
+ gfxredir = gfxredir_server_context_new(NULL);
+ gfxredir_server_context_free(gfxredir);
+#endif // WITH_CHANNEL_GFXREDIR
+#if defined(CHANNEL_AINPUT_SERVER)
+ {
+ ainput_server_context* ainput = ainput_server_context_new(NULL);
+ ainput_server_context_free(ainput);
+ }
+#endif
+}
+
+/**
+ * end of ugly symbols import workaround
+ */
diff --git a/channels/server/channels.h b/channels/server/channels.h
new file mode 100644
index 0000000..a6c4791
--- /dev/null
+++ b/channels/server/channels.h
@@ -0,0 +1,24 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Server Channels
+ *
+ * Copyright 2011-2012 Vic Lee
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_SERVER_CHANNELS_H
+#define FREERDP_CHANNEL_SERVER_CHANNELS_H
+
+#endif /* FREERDP_CHANNEL_SERVER_CHANNELS_H */
diff --git a/channels/smartcard/CMakeLists.txt b/channels/smartcard/CMakeLists.txt
new file mode 100644
index 0000000..98c6c72
--- /dev/null
+++ b/channels/smartcard/CMakeLists.txt
@@ -0,0 +1,22 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("smartcard")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/smartcard/ChannelOptions.cmake b/channels/smartcard/ChannelOptions.cmake
new file mode 100644
index 0000000..7af6e31
--- /dev/null
+++ b/channels/smartcard/ChannelOptions.cmake
@@ -0,0 +1,12 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT OFF)
+
+define_channel_options(NAME "smartcard" TYPE "device"
+ DESCRIPTION "Smart Card Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPESC]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
diff --git a/channels/smartcard/client/CMakeLists.txt b/channels/smartcard/client/CMakeLists.txt
new file mode 100644
index 0000000..08ce6af
--- /dev/null
+++ b/channels/smartcard/client/CMakeLists.txt
@@ -0,0 +1,33 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("smartcard")
+
+set(${MODULE_PREFIX}_SRCS
+ smartcard_main.c
+ smartcard_main.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr freerdp ${OPENSSL_LIBRARIES}
+)
+if (WITH_SMARTCARD_EMULATE)
+ list(APPEND ${MODULE_PREFIX}_LIBS
+ ZLIB::ZLIB
+ )
+endif()
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DeviceServiceEntry")
diff --git a/channels/smartcard/client/smartcard_main.c b/channels/smartcard/client/smartcard_main.c
new file mode 100644
index 0000000..0037b4e
--- /dev/null
+++ b/channels/smartcard/client/smartcard_main.c
@@ -0,0 +1,720 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Smartcard Device Service Virtual Channel
+ *
+ * Copyright 2011 O.S. Systems Software Ltda.
+ * Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
+ * Copyright 2011 Anthony Tong <atong@trustedcs.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/smartcard.h>
+#include <winpr/environment.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/channels/scard.h>
+#include <freerdp/utils/smartcard_call.h>
+#include <freerdp/utils/smartcard_operations.h>
+#include <freerdp/utils/rdpdr_utils.h>
+
+#include "smartcard_main.h"
+
+#define CAST_FROM_DEVICE(device) cast_device_from(device, __func__, __FILE__, __LINE__)
+
+typedef struct
+{
+ SMARTCARD_OPERATION operation;
+ IRP* irp;
+} scard_irp_queue_element;
+
+static SMARTCARD_DEVICE* sSmartcard = NULL;
+
+static void smartcard_context_free(void* pCtx);
+
+static UINT smartcard_complete_irp(SMARTCARD_DEVICE* smartcard, IRP* irp, BOOL* handled);
+
+static SMARTCARD_DEVICE* cast_device_from(DEVICE* device, const char* fkt, const char* file,
+ size_t line)
+{
+ if (!device)
+ {
+ WLog_ERR(TAG, "%s [%s:%" PRIuz "] Called smartcard channel with NULL device", fkt, file,
+ line);
+ return NULL;
+ }
+
+ if (device->type != RDPDR_DTYP_SMARTCARD)
+ {
+ WLog_ERR(TAG,
+ "%s [%s:%" PRIuz "] Called smartcard channel with invalid device of type %" PRIx32,
+ fkt, file, line, device->type);
+ return NULL;
+ }
+
+ return (SMARTCARD_DEVICE*)device;
+}
+
+static DWORD WINAPI smartcard_context_thread(LPVOID arg)
+{
+ SMARTCARD_CONTEXT* pContext = (SMARTCARD_CONTEXT*)arg;
+ DWORD nCount = 0;
+ LONG status = 0;
+ DWORD waitStatus = 0;
+ HANDLE hEvents[2] = { 0 };
+ wMessage message = { 0 };
+ SMARTCARD_DEVICE* smartcard = NULL;
+ UINT error = CHANNEL_RC_OK;
+ smartcard = pContext->smartcard;
+
+ hEvents[nCount++] = MessageQueue_Event(pContext->IrpQueue);
+
+ while (1)
+ {
+ waitStatus = WaitForMultipleObjects(nCount, hEvents, FALSE, INFINITE);
+
+ if (waitStatus == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error);
+ break;
+ }
+
+ waitStatus = WaitForSingleObject(MessageQueue_Event(pContext->IrpQueue), 0);
+
+ if (waitStatus == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
+ break;
+ }
+
+ if (waitStatus == WAIT_OBJECT_0)
+ {
+ scard_irp_queue_element* element = NULL;
+
+ if (!MessageQueue_Peek(pContext->IrpQueue, &message, TRUE))
+ {
+ WLog_ERR(TAG, "MessageQueue_Peek failed!");
+ status = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (message.id == WMQ_QUIT)
+ break;
+
+ element = (scard_irp_queue_element*)message.wParam;
+
+ if (element)
+ {
+ BOOL handled = FALSE;
+ WINPR_ASSERT(smartcard);
+
+ if ((status = smartcard_irp_device_control_call(
+ smartcard->callctx, element->irp->output, &element->irp->IoStatus,
+ &element->operation)))
+ {
+ element->irp->Discard(element->irp);
+ smartcard_operation_free(&element->operation, TRUE);
+ WLog_ERR(TAG, "smartcard_irp_device_control_call failed with error %" PRIu32 "",
+ status);
+ break;
+ }
+
+ error = smartcard_complete_irp(smartcard, element->irp, &handled);
+ if (!handled)
+ element->irp->Discard(element->irp);
+ smartcard_operation_free(&element->operation, TRUE);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "Queue_Enqueue failed!");
+ break;
+ }
+ }
+ }
+ }
+
+ if (status && smartcard->rdpcontext)
+ setChannelError(smartcard->rdpcontext, error, "smartcard_context_thread reported an error");
+
+ ExitThread(status);
+ return error;
+}
+
+static void smartcard_operation_queue_free(void* obj)
+{
+ wMessage* msg = obj;
+ if (!msg)
+ return;
+ if (msg->id != 0)
+ return;
+
+ scard_irp_queue_element* element = (scard_irp_queue_element*)msg->wParam;
+ if (!element)
+ return;
+ WINPR_ASSERT(element->irp);
+ WINPR_ASSERT(element->irp->Discard);
+ element->irp->Discard(element->irp);
+ smartcard_operation_free(&element->operation, TRUE);
+}
+
+static void* smartcard_context_new(void* smartcard, SCARDCONTEXT hContext)
+{
+ SMARTCARD_CONTEXT* pContext = NULL;
+ pContext = (SMARTCARD_CONTEXT*)calloc(1, sizeof(SMARTCARD_CONTEXT));
+
+ if (!pContext)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return pContext;
+ }
+
+ pContext->smartcard = smartcard;
+ pContext->hContext = hContext;
+ pContext->IrpQueue = MessageQueue_New(NULL);
+
+ if (!pContext->IrpQueue)
+ {
+ WLog_ERR(TAG, "MessageQueue_New failed!");
+ goto fail;
+ }
+ wObject* obj = MessageQueue_Object(pContext->IrpQueue);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = smartcard_operation_queue_free;
+
+ pContext->thread = CreateThread(NULL, 0, smartcard_context_thread, pContext, 0, NULL);
+
+ if (!pContext->thread)
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ goto fail;
+ }
+
+ return pContext;
+fail:
+ smartcard_context_free(pContext);
+ return NULL;
+}
+
+void smartcard_context_free(void* pCtx)
+{
+ SMARTCARD_CONTEXT* pContext = pCtx;
+
+ if (!pContext)
+ return;
+
+ /* cancel blocking calls like SCardGetStatusChange */
+ WINPR_ASSERT(pContext->smartcard);
+ smartcard_call_cancel_context(pContext->smartcard->callctx, pContext->hContext);
+
+ if (pContext->IrpQueue)
+ {
+ if (MessageQueue_PostQuit(pContext->IrpQueue, 0))
+ {
+ if (WaitForSingleObject(pContext->thread, INFINITE) == WAIT_FAILED)
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", GetLastError());
+
+ CloseHandle(pContext->thread);
+ }
+ MessageQueue_Free(pContext->IrpQueue);
+ }
+ smartcard_call_release_context(pContext->smartcard->callctx, pContext->hContext);
+ free(pContext);
+}
+
+static void smartcard_release_all_contexts(SMARTCARD_DEVICE* smartcard)
+{
+ smartcard_call_cancel_all_context(smartcard->callctx);
+}
+
+static UINT smartcard_free_(SMARTCARD_DEVICE* smartcard)
+{
+ if (!smartcard)
+ return CHANNEL_RC_OK;
+
+ if (smartcard->IrpQueue)
+ {
+ MessageQueue_Free(smartcard->IrpQueue);
+ CloseHandle(smartcard->thread);
+ }
+
+ Stream_Free(smartcard->device.data, TRUE);
+ ListDictionary_Free(smartcard->rgOutstandingMessages);
+
+ smartcard_call_context_free(smartcard->callctx);
+
+ free(smartcard);
+ return CHANNEL_RC_OK;
+}
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT smartcard_free(DEVICE* device)
+{
+ SMARTCARD_DEVICE* smartcard = CAST_FROM_DEVICE(device);
+
+ if (!smartcard)
+ return ERROR_INVALID_PARAMETER;
+
+ /**
+ * Calling smartcard_release_all_contexts to unblock all operations waiting for transactions
+ * to unlock.
+ */
+ smartcard_release_all_contexts(smartcard);
+
+ /* Stopping all threads and cancelling all IRPs */
+
+ if (smartcard->IrpQueue)
+ {
+ if (MessageQueue_PostQuit(smartcard->IrpQueue, 0) &&
+ (WaitForSingleObject(smartcard->thread, INFINITE) == WAIT_FAILED))
+ {
+ DWORD error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
+ return error;
+ }
+ }
+
+ if (sSmartcard == smartcard)
+ sSmartcard = NULL;
+
+ return smartcard_free_(smartcard);
+}
+
+/**
+ * Initialization occurs when the protocol server sends a device announce message.
+ * At that time, we need to cancel all outstanding IRPs.
+ */
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT smartcard_init(DEVICE* device)
+{
+ SMARTCARD_DEVICE* smartcard = CAST_FROM_DEVICE(device);
+
+ if (!smartcard)
+ return ERROR_INVALID_PARAMETER;
+
+ smartcard_release_all_contexts(smartcard);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT smartcard_complete_irp(SMARTCARD_DEVICE* smartcard, IRP* irp, BOOL* handled)
+{
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(irp);
+ WINPR_ASSERT(handled);
+
+ uintptr_t key = (uintptr_t)irp->CompletionId + 1;
+ ListDictionary_Remove(smartcard->rgOutstandingMessages, (void*)key);
+
+ WINPR_ASSERT(irp->Complete);
+ *handled = TRUE;
+ return irp->Complete(irp);
+}
+
+/**
+ * Multiple threads and SCardGetStatusChange:
+ * http://musclecard.996296.n3.nabble.com/Multiple-threads-and-SCardGetStatusChange-td4430.html
+ */
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT smartcard_process_irp(SMARTCARD_DEVICE* smartcard, IRP* irp, BOOL* handled)
+{
+ LONG status = 0;
+ BOOL asyncIrp = FALSE;
+ SMARTCARD_CONTEXT* pContext = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(handled);
+ WINPR_ASSERT(irp);
+ WINPR_ASSERT(irp->Complete);
+
+ uintptr_t key = (uintptr_t)irp->CompletionId + 1;
+
+ if (!ListDictionary_Add(smartcard->rgOutstandingMessages, (void*)key, irp))
+ {
+ WLog_ERR(TAG, "ListDictionary_Add failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (irp->MajorFunction == IRP_MJ_DEVICE_CONTROL)
+ {
+ scard_irp_queue_element* element = calloc(1, sizeof(scard_irp_queue_element));
+ if (!element)
+ return ERROR_OUTOFMEMORY;
+
+ element->irp = irp;
+ element->operation.completionID = irp->CompletionId;
+
+ status = smartcard_irp_device_control_decode(irp->input, irp->CompletionId, irp->FileId,
+ &element->operation);
+
+ if (status != SCARD_S_SUCCESS)
+ {
+ UINT error = 0;
+
+ smartcard_operation_free(&element->operation, TRUE);
+ irp->IoStatus = (UINT32)STATUS_UNSUCCESSFUL;
+
+ if ((error = smartcard_complete_irp(smartcard, irp, handled)))
+ {
+ WLog_ERR(TAG, "Queue_Enqueue failed!");
+ return error;
+ }
+
+ return CHANNEL_RC_OK;
+ }
+
+ asyncIrp = TRUE;
+
+ switch (element->operation.ioControlCode)
+ {
+ case SCARD_IOCTL_ESTABLISHCONTEXT:
+ case SCARD_IOCTL_RELEASECONTEXT:
+ case SCARD_IOCTL_ISVALIDCONTEXT:
+ case SCARD_IOCTL_CANCEL:
+ case SCARD_IOCTL_ACCESSSTARTEDEVENT:
+ case SCARD_IOCTL_RELEASETARTEDEVENT:
+ asyncIrp = FALSE;
+ break;
+
+ case SCARD_IOCTL_LISTREADERGROUPSA:
+ case SCARD_IOCTL_LISTREADERGROUPSW:
+ case SCARD_IOCTL_LISTREADERSA:
+ case SCARD_IOCTL_LISTREADERSW:
+ case SCARD_IOCTL_INTRODUCEREADERGROUPA:
+ case SCARD_IOCTL_INTRODUCEREADERGROUPW:
+ case SCARD_IOCTL_FORGETREADERGROUPA:
+ case SCARD_IOCTL_FORGETREADERGROUPW:
+ case SCARD_IOCTL_INTRODUCEREADERA:
+ case SCARD_IOCTL_INTRODUCEREADERW:
+ case SCARD_IOCTL_FORGETREADERA:
+ case SCARD_IOCTL_FORGETREADERW:
+ case SCARD_IOCTL_ADDREADERTOGROUPA:
+ case SCARD_IOCTL_ADDREADERTOGROUPW:
+ case SCARD_IOCTL_REMOVEREADERFROMGROUPA:
+ case SCARD_IOCTL_REMOVEREADERFROMGROUPW:
+ case SCARD_IOCTL_LOCATECARDSA:
+ case SCARD_IOCTL_LOCATECARDSW:
+ case SCARD_IOCTL_LOCATECARDSBYATRA:
+ case SCARD_IOCTL_LOCATECARDSBYATRW:
+ case SCARD_IOCTL_READCACHEA:
+ case SCARD_IOCTL_READCACHEW:
+ case SCARD_IOCTL_WRITECACHEA:
+ case SCARD_IOCTL_WRITECACHEW:
+ case SCARD_IOCTL_GETREADERICON:
+ case SCARD_IOCTL_GETDEVICETYPEID:
+ case SCARD_IOCTL_GETSTATUSCHANGEA:
+ case SCARD_IOCTL_GETSTATUSCHANGEW:
+ case SCARD_IOCTL_CONNECTA:
+ case SCARD_IOCTL_CONNECTW:
+ case SCARD_IOCTL_RECONNECT:
+ case SCARD_IOCTL_DISCONNECT:
+ case SCARD_IOCTL_BEGINTRANSACTION:
+ case SCARD_IOCTL_ENDTRANSACTION:
+ case SCARD_IOCTL_STATE:
+ case SCARD_IOCTL_STATUSA:
+ case SCARD_IOCTL_STATUSW:
+ case SCARD_IOCTL_TRANSMIT:
+ case SCARD_IOCTL_CONTROL:
+ case SCARD_IOCTL_GETATTRIB:
+ case SCARD_IOCTL_SETATTRIB:
+ case SCARD_IOCTL_GETTRANSMITCOUNT:
+ asyncIrp = TRUE;
+ break;
+ }
+
+ pContext = smartcard_call_get_context(smartcard->callctx, element->operation.hContext);
+
+ if (!pContext)
+ asyncIrp = FALSE;
+
+ if (!asyncIrp)
+ {
+ UINT error = 0;
+
+ status =
+ smartcard_irp_device_control_call(smartcard->callctx, element->irp->output,
+ &element->irp->IoStatus, &element->operation);
+ smartcard_operation_free(&element->operation, TRUE);
+
+ if (status)
+ {
+ WLog_ERR(TAG, "smartcard_irp_device_control_call failed with error %" PRId32 "!",
+ status);
+ return (UINT32)status;
+ }
+
+ if ((error = smartcard_complete_irp(smartcard, irp, handled)))
+ {
+ WLog_ERR(TAG, "Queue_Enqueue failed!");
+ return error;
+ }
+ }
+ else
+ {
+ if (pContext)
+ {
+ if (!MessageQueue_Post(pContext->IrpQueue, NULL, 0, (void*)element, NULL))
+ {
+ smartcard_operation_free(&element->operation, TRUE);
+ WLog_ERR(TAG, "MessageQueue_Post failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+ *handled = TRUE;
+ }
+ }
+ }
+ else
+ {
+ UINT ustatus = 0;
+ WLog_ERR(TAG, "Unexpected SmartCard IRP: MajorFunction %s, MinorFunction: 0x%08" PRIX32 "",
+ rdpdr_irp_string(irp->MajorFunction), irp->MinorFunction);
+ irp->IoStatus = (UINT32)STATUS_NOT_SUPPORTED;
+
+ if ((ustatus = smartcard_complete_irp(smartcard, irp, handled)))
+ {
+ WLog_ERR(TAG, "Queue_Enqueue failed!");
+ return ustatus;
+ }
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static DWORD WINAPI smartcard_thread_func(LPVOID arg)
+{
+ IRP* irp = NULL;
+ DWORD nCount = 0;
+ DWORD status = 0;
+ HANDLE hEvents[1] = { 0 };
+ wMessage message = { 0 };
+ UINT error = CHANNEL_RC_OK;
+ SMARTCARD_DEVICE* smartcard = CAST_FROM_DEVICE(arg);
+
+ if (!smartcard)
+ return ERROR_INVALID_PARAMETER;
+
+ hEvents[nCount++] = MessageQueue_Event(smartcard->IrpQueue);
+
+ while (1)
+ {
+ status = WaitForMultipleObjects(nCount, hEvents, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error);
+ break;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ {
+ if (!MessageQueue_Peek(smartcard->IrpQueue, &message, TRUE))
+ {
+ WLog_ERR(TAG, "MessageQueue_Peek failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (message.id == WMQ_QUIT)
+ break;
+
+ irp = (IRP*)message.wParam;
+
+ if (irp)
+ {
+ BOOL handled = FALSE;
+ if ((error = smartcard_process_irp(smartcard, irp, &handled)))
+ {
+ WLog_ERR(TAG, "smartcard_process_irp failed with error %" PRIu32 "!", error);
+ goto out;
+ }
+ if (!handled)
+ {
+ WINPR_ASSERT(irp->Discard);
+ irp->Discard(irp);
+ }
+ }
+ }
+ }
+
+out:
+
+ if (error && smartcard->rdpcontext)
+ setChannelError(smartcard->rdpcontext, error, "smartcard_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT smartcard_irp_request(DEVICE* device, IRP* irp)
+{
+ SMARTCARD_DEVICE* smartcard = CAST_FROM_DEVICE(device);
+
+ if (!smartcard)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!MessageQueue_Post(smartcard->IrpQueue, NULL, 0, (void*)irp, NULL))
+ {
+ WLog_ERR(TAG, "MessageQueue_Post failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static void smartcard_free_irp(void* obj)
+{
+ wMessage* msg = obj;
+ if (!msg)
+ return;
+ if (msg->id != 0)
+ return;
+
+ IRP* irp = (IRP*)msg->wParam;
+ if (!irp)
+ return;
+ WINPR_ASSERT(irp->Discard);
+ irp->Discard(irp);
+}
+
+/* smartcard is always built-in */
+#define DeviceServiceEntry smartcard_DeviceServiceEntry
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+extern UINT DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints);
+UINT DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints)
+{
+ SMARTCARD_DEVICE* smartcard = NULL;
+ size_t length = 0;
+ UINT error = CHANNEL_RC_NO_MEMORY;
+
+ if (!sSmartcard)
+ {
+ smartcard = (SMARTCARD_DEVICE*)calloc(1, sizeof(SMARTCARD_DEVICE));
+
+ if (!smartcard)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ smartcard->device.type = RDPDR_DTYP_SMARTCARD;
+ smartcard->device.name = "SCARD";
+ smartcard->device.IRPRequest = smartcard_irp_request;
+ smartcard->device.Init = smartcard_init;
+ smartcard->device.Free = smartcard_free;
+ smartcard->rdpcontext = pEntryPoints->rdpcontext;
+ length = strlen(smartcard->device.name);
+ smartcard->device.data = Stream_New(NULL, length + 1);
+
+ if (!smartcard->device.data)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ goto fail;
+ }
+
+ Stream_Write(smartcard->device.data, "SCARD", 6);
+ smartcard->IrpQueue = MessageQueue_New(NULL);
+
+ if (!smartcard->IrpQueue)
+ {
+ WLog_ERR(TAG, "MessageQueue_New failed!");
+ goto fail;
+ }
+
+ wObject* obj = MessageQueue_Object(smartcard->IrpQueue);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = smartcard_free_irp;
+
+ smartcard->rgOutstandingMessages = ListDictionary_New(TRUE);
+
+ if (!smartcard->rgOutstandingMessages)
+ {
+ WLog_ERR(TAG, "ListDictionary_New failed!");
+ goto fail;
+ }
+
+ smartcard->callctx = smartcard_call_context_new(smartcard->rdpcontext->settings);
+ if (!smartcard->callctx)
+ goto fail;
+
+ if (!smarcard_call_set_callbacks(smartcard->callctx, smartcard, smartcard_context_new,
+ smartcard_context_free))
+ goto fail;
+
+ if ((error = pEntryPoints->RegisterDevice(pEntryPoints->devman, &smartcard->device)))
+ {
+ WLog_ERR(TAG, "RegisterDevice failed!");
+ goto fail;
+ }
+
+ smartcard->thread =
+ CreateThread(NULL, 0, smartcard_thread_func, smartcard, CREATE_SUSPENDED, NULL);
+
+ if (!smartcard->thread)
+ {
+ WLog_ERR(TAG, "ListDictionary_New failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto fail;
+ }
+
+ ResumeThread(smartcard->thread);
+ }
+ else
+ smartcard = sSmartcard;
+
+ if (pEntryPoints->device->Name)
+ {
+ smartcard_call_context_add(smartcard->callctx, pEntryPoints->device->Name);
+ }
+
+ sSmartcard = smartcard;
+ return CHANNEL_RC_OK;
+fail:
+ smartcard_free_(smartcard);
+ return error;
+}
diff --git a/channels/smartcard/client/smartcard_main.h b/channels/smartcard/client/smartcard_main.h
new file mode 100644
index 0000000..c588588
--- /dev/null
+++ b/channels/smartcard/client/smartcard_main.h
@@ -0,0 +1,59 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Smartcard Device Service Virtual Channel
+ *
+ * Copyright 2011 O.S. Systems Software Ltda.
+ * Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_SMARTCARD_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_SMARTCARD_CLIENT_MAIN_H
+
+#include <freerdp/channels/log.h>
+#include <freerdp/channels/rdpdr.h>
+
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+#include <winpr/synch.h>
+#include <winpr/smartcard.h>
+#include <winpr/collections.h>
+
+#include <freerdp/utils/smartcard_operations.h>
+#include <freerdp/utils/smartcard_call.h>
+
+#define TAG CHANNELS_TAG("smartcard.client")
+
+typedef struct
+{
+ DEVICE device;
+
+ HANDLE thread;
+ scard_call_context* callctx;
+ wMessageQueue* IrpQueue;
+ wListDictionary* rgOutstandingMessages;
+ rdpContext* rdpcontext;
+} SMARTCARD_DEVICE;
+
+typedef struct
+{
+ HANDLE thread;
+ SCARDCONTEXT hContext;
+ wMessageQueue* IrpQueue;
+ SMARTCARD_DEVICE* smartcard;
+} SMARTCARD_CONTEXT;
+
+#endif /* FREERDP_CHANNEL_SMARTCARD_CLIENT_MAIN_H */
diff --git a/channels/sshagent/CMakeLists.txt b/channels/sshagent/CMakeLists.txt
new file mode 100644
index 0000000..71aab99
--- /dev/null
+++ b/channels/sshagent/CMakeLists.txt
@@ -0,0 +1,23 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+# Copyright 2017 Ben Cohen
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("sshagent")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/sshagent/ChannelOptions.cmake b/channels/sshagent/ChannelOptions.cmake
new file mode 100644
index 0000000..41b5a21
--- /dev/null
+++ b/channels/sshagent/ChannelOptions.cmake
@@ -0,0 +1,12 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT OFF)
+set(OPTION_SERVER_DEFAULT OFF)
+
+define_channel_options(NAME "sshagent" TYPE "dynamic"
+ DESCRIPTION "SSH Agent Forwarding (experimental)"
+ SPECIFICATIONS ""
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+
diff --git a/channels/sshagent/client/CMakeLists.txt b/channels/sshagent/client/CMakeLists.txt
new file mode 100644
index 0000000..daad106
--- /dev/null
+++ b/channels/sshagent/client/CMakeLists.txt
@@ -0,0 +1,32 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+# Copyright 2017 Ben Cohen
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("sshagent")
+
+set(${MODULE_PREFIX}_SRCS
+ sshagent_main.c
+ sshagent_main.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+)
+
+include_directories(..)
+
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
diff --git a/channels/sshagent/client/sshagent_main.c b/channels/sshagent/client/sshagent_main.c
new file mode 100644
index 0000000..9ee5102
--- /dev/null
+++ b/channels/sshagent/client/sshagent_main.c
@@ -0,0 +1,375 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SSH Agent Virtual Channel Extension
+ *
+ * Copyright 2013 Christian Hofstaedtler
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2017 Ben Cohen
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * sshagent_main.c: DVC plugin to forward queries from RDP to the ssh-agent
+ *
+ * This relays data to and from an ssh-agent program equivalent running on the
+ * RDP server to an ssh-agent running locally. Unlike the normal ssh-agent,
+ * which sends data over an SSH channel, the data is send over an RDP dynamic
+ * virtual channel.
+ *
+ * protocol specification:
+ * Forward data verbatim over RDP dynamic virtual channel named "sshagent"
+ * between a ssh client on the xrdp server and the real ssh-agent where
+ * the RDP client is running. Each connection by a separate client to
+ * xrdp-ssh-agent gets a separate DVC invocation.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+
+#include "sshagent_main.h"
+
+#include <freerdp/freerdp.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("sshagent.client")
+
+typedef struct
+{
+ IWTSListenerCallback iface;
+
+ IWTSPlugin* plugin;
+ IWTSVirtualChannelManager* channel_mgr;
+
+ rdpContext* rdpcontext;
+ const char* agent_uds_path;
+} SSHAGENT_LISTENER_CALLBACK;
+
+typedef struct
+{
+ GENERIC_CHANNEL_CALLBACK generic;
+
+ rdpContext* rdpcontext;
+ int agent_fd;
+ HANDLE thread;
+ CRITICAL_SECTION lock;
+} SSHAGENT_CHANNEL_CALLBACK;
+
+typedef struct
+{
+ IWTSPlugin iface;
+
+ SSHAGENT_LISTENER_CALLBACK* listener_callback;
+
+ rdpContext* rdpcontext;
+} SSHAGENT_PLUGIN;
+
+/**
+ * Function to open the connection to the sshagent
+ *
+ * @return The fd on success, otherwise -1
+ */
+static int connect_to_sshagent(const char* udspath)
+{
+ int agent_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
+ if (agent_fd == -1)
+ {
+ WLog_ERR(TAG, "Can't open Unix domain socket!");
+ return -1;
+ }
+
+ struct sockaddr_un addr = { 0 };
+
+ addr.sun_family = AF_UNIX;
+
+ strncpy(addr.sun_path, udspath, sizeof(addr.sun_path) - 1);
+
+ int rc = connect(agent_fd, (struct sockaddr*)&addr, sizeof(addr));
+
+ if (rc != 0)
+ {
+ WLog_ERR(TAG, "Can't connect to Unix domain socket \"%s\"!", udspath);
+ close(agent_fd);
+ return -1;
+ }
+
+ return agent_fd;
+}
+
+/**
+ * Entry point for thread to read from the ssh-agent socket and forward
+ * the data to RDP
+ *
+ * @return NULL
+ */
+static DWORD WINAPI sshagent_read_thread(LPVOID data)
+{
+ SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*)data;
+ BYTE buffer[4096];
+ int going = 1;
+ UINT status = CHANNEL_RC_OK;
+
+ while (going)
+ {
+ int bytes_read = read(callback->agent_fd, buffer, sizeof(buffer));
+
+ if (bytes_read == 0)
+ {
+ /* Socket closed cleanly at other end */
+ going = 0;
+ }
+ else if (bytes_read < 0)
+ {
+ if (errno != EINTR)
+ {
+ WLog_ERR(TAG, "Error reading from sshagent, errno=%d", errno);
+ status = ERROR_READ_FAULT;
+ going = 0;
+ }
+ }
+ else
+ {
+ /* Something read: forward to virtual channel */
+ IWTSVirtualChannel* channel = callback->generic.channel;
+ status = channel->Write(channel, bytes_read, buffer, NULL);
+
+ if (status != CHANNEL_RC_OK)
+ {
+ going = 0;
+ }
+ }
+ }
+
+ close(callback->agent_fd);
+
+ if (status != CHANNEL_RC_OK)
+ setChannelError(callback->rdpcontext, status, "sshagent_read_thread reported an error");
+
+ ExitThread(status);
+ return status;
+}
+
+/**
+ * Callback for data received from the RDP server; forward this to ssh-agent
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT sshagent_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
+{
+ SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*)pChannelCallback;
+ BYTE* pBuffer = Stream_Pointer(data);
+ UINT32 cbSize = Stream_GetRemainingLength(data);
+ BYTE* pos = pBuffer;
+ /* Forward what we have received to the ssh agent */
+ UINT32 bytes_to_write = cbSize;
+ errno = 0;
+
+ while (bytes_to_write > 0)
+ {
+ int bytes_written = write(callback->agent_fd, pos, bytes_to_write);
+
+ if (bytes_written < 0)
+ {
+ if (errno != EINTR)
+ {
+ WLog_ERR(TAG, "Error writing to sshagent, errno=%d", errno);
+ return ERROR_WRITE_FAULT;
+ }
+ }
+ else
+ {
+ bytes_to_write -= bytes_written;
+ pos += bytes_written;
+ }
+ }
+
+ /* Consume stream */
+ Stream_Seek(data, cbSize);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Callback for when the virtual channel is closed
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT sshagent_on_close(IWTSVirtualChannelCallback* pChannelCallback)
+{
+ SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*)pChannelCallback;
+ /* Call shutdown() to wake up the read() in sshagent_read_thread(). */
+ shutdown(callback->agent_fd, SHUT_RDWR);
+ EnterCriticalSection(&callback->lock);
+
+ if (WaitForSingleObject(callback->thread, INFINITE) == WAIT_FAILED)
+ {
+ UINT error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ CloseHandle(callback->thread);
+ LeaveCriticalSection(&callback->lock);
+ DeleteCriticalSection(&callback->lock);
+ free(callback);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Callback for when a new virtual channel is opened
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT sshagent_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
+ IWTSVirtualChannel* pChannel, BYTE* Data,
+ BOOL* pbAccept,
+ IWTSVirtualChannelCallback** ppCallback)
+{
+ SSHAGENT_CHANNEL_CALLBACK* callback;
+ GENERIC_CHANNEL_CALLBACK* generic;
+ SSHAGENT_LISTENER_CALLBACK* listener_callback = (SSHAGENT_LISTENER_CALLBACK*)pListenerCallback;
+ callback = (SSHAGENT_CHANNEL_CALLBACK*)calloc(1, sizeof(SSHAGENT_CHANNEL_CALLBACK));
+
+ if (!callback)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ /* Now open a connection to the local ssh-agent. Do this for each
+ * connection to the plugin in case we mess up the agent session. */
+ callback->agent_fd = connect_to_sshagent(listener_callback->agent_uds_path);
+
+ if (callback->agent_fd == -1)
+ {
+ free(callback);
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+ }
+
+ InitializeCriticalSection(&callback->lock);
+ generic = &callback->generic;
+ generic->iface.OnDataReceived = sshagent_on_data_received;
+ generic->iface.OnClose = sshagent_on_close;
+ generic->plugin = listener_callback->plugin;
+ generic->channel_mgr = listener_callback->channel_mgr;
+ generic->channel = pChannel;
+ callback->rdpcontext = listener_callback->rdpcontext;
+ callback->thread = CreateThread(NULL, 0, sshagent_read_thread, (void*)callback, 0, NULL);
+
+ if (!callback->thread)
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ DeleteCriticalSection(&callback->lock);
+ free(callback);
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+ }
+
+ *ppCallback = (IWTSVirtualChannelCallback*)callback;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Callback for when the plugin is initialised
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT sshagent_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr)
+{
+ SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*)pPlugin;
+ sshagent->listener_callback =
+ (SSHAGENT_LISTENER_CALLBACK*)calloc(1, sizeof(SSHAGENT_LISTENER_CALLBACK));
+
+ if (!sshagent->listener_callback)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ sshagent->listener_callback->rdpcontext = sshagent->rdpcontext;
+ sshagent->listener_callback->iface.OnNewChannelConnection = sshagent_on_new_channel_connection;
+ sshagent->listener_callback->plugin = pPlugin;
+ sshagent->listener_callback->channel_mgr = pChannelMgr;
+ sshagent->listener_callback->agent_uds_path = getenv("SSH_AUTH_SOCK");
+
+ if (sshagent->listener_callback->agent_uds_path == NULL)
+ {
+ WLog_ERR(TAG, "Environment variable $SSH_AUTH_SOCK undefined!");
+ free(sshagent->listener_callback);
+ sshagent->listener_callback = NULL;
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+ }
+
+ return pChannelMgr->CreateListener(pChannelMgr, "SSHAGENT", 0,
+ (IWTSListenerCallback*)sshagent->listener_callback, NULL);
+}
+
+/**
+ * Callback for when the plugin is terminated
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT sshagent_plugin_terminated(IWTSPlugin* pPlugin)
+{
+ SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*)pPlugin;
+ free(sshagent);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Main entry point for sshagent DVC plugin
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT sshagent_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
+{
+ UINT status = CHANNEL_RC_OK;
+ SSHAGENT_PLUGIN* sshagent;
+ sshagent = (SSHAGENT_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, "sshagent");
+
+ if (!sshagent)
+ {
+ sshagent = (SSHAGENT_PLUGIN*)calloc(1, sizeof(SSHAGENT_PLUGIN));
+
+ if (!sshagent)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ sshagent->iface.Initialize = sshagent_plugin_initialize;
+ sshagent->iface.Connected = NULL;
+ sshagent->iface.Disconnected = NULL;
+ sshagent->iface.Terminated = sshagent_plugin_terminated;
+ sshagent->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints);
+ status = pEntryPoints->RegisterPlugin(pEntryPoints, "sshagent", &sshagent->iface);
+ }
+
+ return status;
+}
+
+/* vim: set sw=8:ts=8:noet: */
diff --git a/channels/sshagent/client/sshagent_main.h b/channels/sshagent/client/sshagent_main.h
new file mode 100644
index 0000000..1fcc214
--- /dev/null
+++ b/channels/sshagent/client/sshagent_main.h
@@ -0,0 +1,42 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SSH Agent Virtual Channel Extension
+ *
+ * Copyright 2013 Christian Hofstaedtler
+ * Copyright 2017 Ben Cohen
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SSHAGENT_MAIN_H
+#define SSHAGENT_MAIN_H
+
+#include <freerdp/config.h>
+
+#include <winpr/stream.h>
+
+#include <freerdp/svc.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/log.h>
+
+#define DVC_TAG CHANNELS_TAG("sshagent.client")
+#ifdef WITH_DEBUG_SSHAGENT
+#define DEBUG_SSHAGENT(...) WLog_DBG(DVC_TAG, __VA_ARGS__)
+#else
+#define DEBUG_SSHAGENT(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#endif /* SSHAGENT_MAIN_H */
diff --git a/channels/telemetry/CMakeLists.txt b/channels/telemetry/CMakeLists.txt
new file mode 100644
index 0000000..d2b4f24
--- /dev/null
+++ b/channels/telemetry/CMakeLists.txt
@@ -0,0 +1,22 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2022 Pascal Nowack <Pascal.Nowack@gmx.de>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("telemetry")
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/telemetry/ChannelOptions.cmake b/channels/telemetry/ChannelOptions.cmake
new file mode 100644
index 0000000..1b9e391
--- /dev/null
+++ b/channels/telemetry/ChannelOptions.cmake
@@ -0,0 +1,12 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT OFF)
+set(OPTION_SERVER_DEFAULT ON)
+
+define_channel_options(NAME "telemetry" TYPE "dynamic"
+ DESCRIPTION "Telemetry Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPET]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/telemetry/server/CMakeLists.txt b/channels/telemetry/server/CMakeLists.txt
new file mode 100644
index 0000000..6977b63
--- /dev/null
+++ b/channels/telemetry/server/CMakeLists.txt
@@ -0,0 +1,28 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2022 Pascal Nowack <Pascal.Nowack@gmx.de>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_server("telemetry")
+
+set(${MODULE_PREFIX}_SRCS
+ telemetry_main.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry")
diff --git a/channels/telemetry/server/telemetry_main.c b/channels/telemetry/server/telemetry_main.c
new file mode 100644
index 0000000..0d8e047
--- /dev/null
+++ b/channels/telemetry/server/telemetry_main.c
@@ -0,0 +1,442 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Telemetry Virtual Channel Extension
+ *
+ * Copyright 2022 Pascal Nowack <Pascal.Nowack@gmx.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/server/telemetry.h>
+
+#define TAG CHANNELS_TAG("telemetry.server")
+
+typedef enum
+{
+ TELEMETRY_INITIAL,
+ TELEMETRY_OPENED,
+} eTelemetryChannelState;
+
+typedef struct
+{
+ TelemetryServerContext context;
+
+ HANDLE stopEvent;
+
+ HANDLE thread;
+ void* telemetry_channel;
+
+ DWORD SessionId;
+
+ BOOL isOpened;
+ BOOL externalThread;
+
+ /* Channel state */
+ eTelemetryChannelState state;
+
+ wStream* buffer;
+} telemetry_server;
+
+static UINT telemetry_server_initialize(TelemetryServerContext* context, BOOL externalThread)
+{
+ UINT error = CHANNEL_RC_OK;
+ telemetry_server* telemetry = (telemetry_server*)context;
+
+ WINPR_ASSERT(telemetry);
+
+ if (telemetry->isOpened)
+ {
+ WLog_WARN(TAG, "Application error: TELEMETRY channel already initialized, "
+ "calling in this state is not possible!");
+ return ERROR_INVALID_STATE;
+ }
+
+ telemetry->externalThread = externalThread;
+
+ return error;
+}
+
+static UINT telemetry_server_open_channel(telemetry_server* telemetry)
+{
+ TelemetryServerContext* context = &telemetry->context;
+ DWORD Error = ERROR_SUCCESS;
+ HANDLE hEvent = NULL;
+ DWORD BytesReturned = 0;
+ PULONG pSessionId = NULL;
+ UINT32 channelId = 0;
+ BOOL status = TRUE;
+
+ WINPR_ASSERT(telemetry);
+
+ if (WTSQuerySessionInformationA(telemetry->context.vcm, WTS_CURRENT_SESSION, WTSSessionId,
+ (LPSTR*)&pSessionId, &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSQuerySessionInformationA failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ telemetry->SessionId = (DWORD)*pSessionId;
+ WTSFreeMemory(pSessionId);
+ hEvent = WTSVirtualChannelManagerGetEventHandle(telemetry->context.vcm);
+
+ if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED)
+ {
+ Error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error);
+ return Error;
+ }
+
+ telemetry->telemetry_channel = WTSVirtualChannelOpenEx(
+ telemetry->SessionId, TELEMETRY_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC);
+ if (!telemetry->telemetry_channel)
+ {
+ Error = GetLastError();
+ WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", Error);
+ return Error;
+ }
+
+ channelId = WTSChannelGetIdByHandle(telemetry->telemetry_channel);
+
+ IFCALLRET(context->ChannelIdAssigned, status, context, channelId);
+ if (!status)
+ {
+ WLog_ERR(TAG, "context->ChannelIdAssigned failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return Error;
+}
+
+static UINT telemetry_server_recv_rdp_telemetry_pdu(TelemetryServerContext* context, wStream* s)
+{
+ TELEMETRY_RDP_TELEMETRY_PDU pdu;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 16))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT32(s, pdu.PromptForCredentialsMillis);
+ Stream_Read_UINT32(s, pdu.PromptForCredentialsDoneMillis);
+ Stream_Read_UINT32(s, pdu.GraphicsChannelOpenedMillis);
+ Stream_Read_UINT32(s, pdu.FirstGraphicsReceivedMillis);
+
+ IFCALLRET(context->RdpTelemetry, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->RdpTelemetry failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT telemetry_process_message(telemetry_server* telemetry)
+{
+ BOOL rc = 0;
+ UINT error = ERROR_INTERNAL_ERROR;
+ ULONG BytesReturned = 0;
+ BYTE MessageId = 0;
+ BYTE Length = 0;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(telemetry);
+ WINPR_ASSERT(telemetry->telemetry_channel);
+
+ s = telemetry->buffer;
+ WINPR_ASSERT(s);
+
+ Stream_SetPosition(s, 0);
+ rc = WTSVirtualChannelRead(telemetry->telemetry_channel, 0, NULL, 0, &BytesReturned);
+ if (!rc)
+ goto out;
+
+ if (BytesReturned < 1)
+ {
+ error = CHANNEL_RC_OK;
+ goto out;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ if (WTSVirtualChannelRead(telemetry->telemetry_channel, 0, (PCHAR)Stream_Buffer(s),
+ (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ goto out;
+ }
+
+ Stream_SetLength(s, BytesReturned);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT8(s, MessageId);
+ Stream_Read_UINT8(s, Length);
+
+ switch (MessageId)
+ {
+ case 0x01:
+ error = telemetry_server_recv_rdp_telemetry_pdu(&telemetry->context, s);
+ break;
+ default:
+ WLog_ERR(TAG, "telemetry_process_message: unknown MessageId %" PRIu8 "", MessageId);
+ break;
+ }
+
+out:
+ if (error)
+ WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+static UINT telemetry_server_context_poll_int(TelemetryServerContext* context)
+{
+ telemetry_server* telemetry = (telemetry_server*)context;
+ UINT error = ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(telemetry);
+
+ switch (telemetry->state)
+ {
+ case TELEMETRY_INITIAL:
+ error = telemetry_server_open_channel(telemetry);
+ if (error)
+ WLog_ERR(TAG, "telemetry_server_open_channel failed with error %" PRIu32 "!",
+ error);
+ else
+ telemetry->state = TELEMETRY_OPENED;
+ break;
+ case TELEMETRY_OPENED:
+ error = telemetry_process_message(telemetry);
+ break;
+ }
+
+ return error;
+}
+
+static HANDLE telemetry_server_get_channel_handle(telemetry_server* telemetry)
+{
+ void* buffer = NULL;
+ DWORD BytesReturned = 0;
+ HANDLE ChannelEvent = NULL;
+
+ WINPR_ASSERT(telemetry);
+
+ if (WTSVirtualChannelQuery(telemetry->telemetry_channel, WTSVirtualEventHandle, &buffer,
+ &BytesReturned) == TRUE)
+ {
+ if (BytesReturned == sizeof(HANDLE))
+ CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE));
+
+ WTSFreeMemory(buffer);
+ }
+
+ return ChannelEvent;
+}
+
+static DWORD WINAPI telemetry_server_thread_func(LPVOID arg)
+{
+ DWORD nCount = 0;
+ HANDLE events[2] = { 0 };
+ telemetry_server* telemetry = (telemetry_server*)arg;
+ UINT error = CHANNEL_RC_OK;
+ DWORD status = 0;
+
+ WINPR_ASSERT(telemetry);
+
+ nCount = 0;
+ events[nCount++] = telemetry->stopEvent;
+
+ while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0))
+ {
+ switch (telemetry->state)
+ {
+ case TELEMETRY_INITIAL:
+ error = telemetry_server_context_poll_int(&telemetry->context);
+ if (error == CHANNEL_RC_OK)
+ {
+ events[1] = telemetry_server_get_channel_handle(telemetry);
+ nCount = 2;
+ }
+ break;
+ case TELEMETRY_OPENED:
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+ switch (status)
+ {
+ case WAIT_OBJECT_0:
+ break;
+ case WAIT_OBJECT_0 + 1:
+ case WAIT_TIMEOUT:
+ error = telemetry_server_context_poll_int(&telemetry->context);
+ break;
+
+ case WAIT_FAILED:
+ default:
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+ break;
+ }
+ }
+
+ WTSVirtualChannelClose(telemetry->telemetry_channel);
+ telemetry->telemetry_channel = NULL;
+
+ if (error && telemetry->context.rdpcontext)
+ setChannelError(telemetry->context.rdpcontext, error,
+ "telemetry_server_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+static UINT telemetry_server_open(TelemetryServerContext* context)
+{
+ telemetry_server* telemetry = (telemetry_server*)context;
+
+ WINPR_ASSERT(telemetry);
+
+ if (!telemetry->externalThread && (telemetry->thread == NULL))
+ {
+ telemetry->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!telemetry->stopEvent)
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ telemetry->thread = CreateThread(NULL, 0, telemetry_server_thread_func, telemetry, 0, NULL);
+ if (!telemetry->thread)
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ CloseHandle(telemetry->stopEvent);
+ telemetry->stopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+ telemetry->isOpened = TRUE;
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT telemetry_server_close(TelemetryServerContext* context)
+{
+ UINT error = CHANNEL_RC_OK;
+ telemetry_server* telemetry = (telemetry_server*)context;
+
+ WINPR_ASSERT(telemetry);
+
+ if (!telemetry->externalThread && telemetry->thread)
+ {
+ SetEvent(telemetry->stopEvent);
+
+ if (WaitForSingleObject(telemetry->thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ CloseHandle(telemetry->thread);
+ CloseHandle(telemetry->stopEvent);
+ telemetry->thread = NULL;
+ telemetry->stopEvent = NULL;
+ }
+ if (telemetry->externalThread)
+ {
+ if (telemetry->state != TELEMETRY_INITIAL)
+ {
+ WTSVirtualChannelClose(telemetry->telemetry_channel);
+ telemetry->telemetry_channel = NULL;
+ telemetry->state = TELEMETRY_INITIAL;
+ }
+ }
+ telemetry->isOpened = FALSE;
+
+ return error;
+}
+
+static UINT telemetry_server_context_poll(TelemetryServerContext* context)
+{
+ telemetry_server* telemetry = (telemetry_server*)context;
+
+ WINPR_ASSERT(telemetry);
+
+ if (!telemetry->externalThread)
+ return ERROR_INTERNAL_ERROR;
+
+ return telemetry_server_context_poll_int(context);
+}
+
+static BOOL telemetry_server_context_handle(TelemetryServerContext* context, HANDLE* handle)
+{
+ telemetry_server* telemetry = (telemetry_server*)context;
+
+ WINPR_ASSERT(telemetry);
+ WINPR_ASSERT(handle);
+
+ if (!telemetry->externalThread)
+ return FALSE;
+ if (telemetry->state == TELEMETRY_INITIAL)
+ return FALSE;
+
+ *handle = telemetry_server_get_channel_handle(telemetry);
+
+ return TRUE;
+}
+
+TelemetryServerContext* telemetry_server_context_new(HANDLE vcm)
+{
+ telemetry_server* telemetry = (telemetry_server*)calloc(1, sizeof(telemetry_server));
+
+ if (!telemetry)
+ return NULL;
+
+ telemetry->context.vcm = vcm;
+ telemetry->context.Initialize = telemetry_server_initialize;
+ telemetry->context.Open = telemetry_server_open;
+ telemetry->context.Close = telemetry_server_close;
+ telemetry->context.Poll = telemetry_server_context_poll;
+ telemetry->context.ChannelHandle = telemetry_server_context_handle;
+
+ telemetry->buffer = Stream_New(NULL, 4096);
+ if (!telemetry->buffer)
+ goto fail;
+
+ return &telemetry->context;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ telemetry_server_context_free(&telemetry->context);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void telemetry_server_context_free(TelemetryServerContext* context)
+{
+ telemetry_server* telemetry = (telemetry_server*)context;
+
+ if (telemetry)
+ {
+ telemetry_server_close(context);
+ Stream_Free(telemetry->buffer, TRUE);
+ }
+
+ free(telemetry);
+}
diff --git a/channels/tsmf/CMakeLists.txt b/channels/tsmf/CMakeLists.txt
new file mode 100644
index 0000000..8b4073e
--- /dev/null
+++ b/channels/tsmf/CMakeLists.txt
@@ -0,0 +1,22 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("tsmf")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/tsmf/ChannelOptions.cmake b/channels/tsmf/ChannelOptions.cmake
new file mode 100644
index 0000000..b5252ea
--- /dev/null
+++ b/channels/tsmf/ChannelOptions.cmake
@@ -0,0 +1,23 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT OFF)
+set(OPTION_SERVER_DEFAULT OFF)
+
+if(WIN32)
+ set(OPTION_CLIENT_DEFAULT OFF)
+ set(OPTION_SERVER_DEFAULT OFF)
+endif()
+
+if(ANDROID)
+ set(OPTION_CLIENT_DEFAULT OFF)
+ set(OPTION_SERVER_DEFAULT OFF)
+endif()
+
+define_channel_options(NAME "tsmf" TYPE "dynamic"
+ DESCRIPTION "[DEPRECATED] Video Redirection Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPEV]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/tsmf/client/CMakeLists.txt b/channels/tsmf/client/CMakeLists.txt
new file mode 100644
index 0000000..ea8e2f0
--- /dev/null
+++ b/channels/tsmf/client/CMakeLists.txt
@@ -0,0 +1,84 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+# Copyright 2012 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("tsmf")
+
+message(DEPRECATION "TSMF channel is no longer maintained. Use [MS-RDPEVOR] (/video) instead.")
+
+
+find_package(PkgConfig)
+if (PkgConfig_FOUND)
+ pkg_check_modules(gstreamer gstreamer-1.0)
+endif()
+
+if (WITH_GSTREAMER_1_0)
+ if(gstreamer_FOUND)
+ add_definitions(-DWITH_GSTREAMER_1_0)
+ else()
+ message(WARNING "gstreamer not detected, disabling support")
+ endif()
+endif()
+
+set(${MODULE_PREFIX}_SRCS
+ tsmf_audio.c
+ tsmf_audio.h
+ tsmf_codec.c
+ tsmf_codec.h
+ tsmf_constants.h
+ tsmf_decoder.c
+ tsmf_decoder.h
+ tsmf_ifman.c
+ tsmf_ifman.h
+ tsmf_main.c
+ tsmf_main.h
+ tsmf_media.c
+ tsmf_media.h
+ tsmf_types.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr freerdp
+)
+include_directories(..)
+
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
+
+if(WITH_VIDEO_FFMPEG)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "ffmpeg" "decoder")
+endif()
+
+if(WITH_GSTREAMER_1_0)
+ find_package(X11)
+ if (X11_Xrandr_FOUND)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "gstreamer" "decoder")
+ else()
+ message(WARNING "Disabling tsmf gstreamer because XRandR wasn't found")
+ endif()
+endif()
+
+if(WITH_OSS)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "oss" "audio")
+endif()
+
+if(WITH_ALSA)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "alsa" "audio")
+endif()
+
+if(WITH_PULSE)
+ add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "pulse" "audio")
+endif()
diff --git a/channels/tsmf/client/alsa/CMakeLists.txt b/channels/tsmf/client/alsa/CMakeLists.txt
new file mode 100644
index 0000000..9910542
--- /dev/null
+++ b/channels/tsmf/client/alsa/CMakeLists.txt
@@ -0,0 +1,35 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("tsmf" "alsa" "audio")
+
+find_package(ALSA REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ tsmf_alsa.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${ALSA_LIBRARIES}
+)
+
+include_directories(..)
+include_directories(${ALSA_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/tsmf/client/alsa/tsmf_alsa.c b/channels/tsmf/client/alsa/tsmf_alsa.c
new file mode 100644
index 0000000..fcf30aa
--- /dev/null
+++ b/channels/tsmf/client/alsa/tsmf_alsa.c
@@ -0,0 +1,240 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - ALSA Audio Device
+ *
+ * Copyright 2010-2011 Vic Lee
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <winpr/crt.h>
+
+#include <alsa/asoundlib.h>
+
+#include <freerdp/types.h>
+#include <freerdp/codec/dsp.h>
+
+#include "tsmf_audio.h"
+
+typedef struct
+{
+ ITSMFAudioDevice iface;
+
+ char device[32];
+ snd_pcm_t* out_handle;
+ UINT32 source_rate;
+ UINT32 actual_rate;
+ UINT32 source_channels;
+ UINT32 actual_channels;
+ UINT32 bytes_per_sample;
+} TSMFAlsaAudioDevice;
+
+static BOOL tsmf_alsa_open_device(TSMFAlsaAudioDevice* alsa)
+{
+ int error = 0;
+ error = snd_pcm_open(&alsa->out_handle, alsa->device, SND_PCM_STREAM_PLAYBACK, 0);
+
+ if (error < 0)
+ {
+ WLog_ERR(TAG, "failed to open device %s", alsa->device);
+ return FALSE;
+ }
+
+ DEBUG_TSMF("open device %s", alsa->device);
+ return TRUE;
+}
+
+static BOOL tsmf_alsa_open(ITSMFAudioDevice* audio, const char* device)
+{
+ TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*)audio;
+
+ if (!device)
+ {
+ strncpy(alsa->device, "default", sizeof(alsa->device));
+ }
+ else
+ {
+ strncpy(alsa->device, device, sizeof(alsa->device) - 1);
+ }
+
+ return tsmf_alsa_open_device(alsa);
+}
+
+static BOOL tsmf_alsa_set_format(ITSMFAudioDevice* audio, UINT32 sample_rate, UINT32 channels,
+ UINT32 bits_per_sample)
+{
+ int error = 0;
+ snd_pcm_uframes_t frames = 0;
+ snd_pcm_hw_params_t* hw_params = NULL;
+ snd_pcm_sw_params_t* sw_params = NULL;
+ TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*)audio;
+
+ if (!alsa->out_handle)
+ return FALSE;
+
+ snd_pcm_drop(alsa->out_handle);
+ alsa->actual_rate = alsa->source_rate = sample_rate;
+ alsa->actual_channels = alsa->source_channels = channels;
+ alsa->bytes_per_sample = bits_per_sample / 8;
+ error = snd_pcm_hw_params_malloc(&hw_params);
+
+ if (error < 0)
+ {
+ WLog_ERR(TAG, "snd_pcm_hw_params_malloc failed");
+ return FALSE;
+ }
+
+ snd_pcm_hw_params_any(alsa->out_handle, hw_params);
+ snd_pcm_hw_params_set_access(alsa->out_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
+ snd_pcm_hw_params_set_format(alsa->out_handle, hw_params, SND_PCM_FORMAT_S16_LE);
+ snd_pcm_hw_params_set_rate_near(alsa->out_handle, hw_params, &alsa->actual_rate, NULL);
+ snd_pcm_hw_params_set_channels_near(alsa->out_handle, hw_params, &alsa->actual_channels);
+ frames = sample_rate;
+ snd_pcm_hw_params_set_buffer_size_near(alsa->out_handle, hw_params, &frames);
+ snd_pcm_hw_params(alsa->out_handle, hw_params);
+ snd_pcm_hw_params_free(hw_params);
+ error = snd_pcm_sw_params_malloc(&sw_params);
+
+ if (error < 0)
+ {
+ WLog_ERR(TAG, "snd_pcm_sw_params_malloc");
+ return FALSE;
+ }
+
+ snd_pcm_sw_params_current(alsa->out_handle, sw_params);
+ snd_pcm_sw_params_set_start_threshold(alsa->out_handle, sw_params, frames / 2);
+ snd_pcm_sw_params(alsa->out_handle, sw_params);
+ snd_pcm_sw_params_free(sw_params);
+ snd_pcm_prepare(alsa->out_handle);
+ DEBUG_TSMF("sample_rate %" PRIu32 " channels %" PRIu32 " bits_per_sample %" PRIu32 "",
+ sample_rate, channels, bits_per_sample);
+ DEBUG_TSMF("hardware buffer %lu frames", frames);
+
+ if ((alsa->actual_rate != alsa->source_rate) ||
+ (alsa->actual_channels != alsa->source_channels))
+ {
+ DEBUG_TSMF("actual rate %" PRIu32 " / channel %" PRIu32 " is different "
+ "from source rate %" PRIu32 " / channel %" PRIu32 ", resampling required.",
+ alsa->actual_rate, alsa->actual_channels, alsa->source_rate,
+ alsa->source_channels);
+ }
+
+ return TRUE;
+}
+
+static BOOL tsmf_alsa_play(ITSMFAudioDevice* audio, const BYTE* src, UINT32 data_size)
+{
+ int len = 0;
+ int error = 0;
+ int frames = 0;
+ const BYTE* end = NULL;
+ const BYTE* pindex = NULL;
+ int rbytes_per_frame = 0;
+ int sbytes_per_frame = 0;
+ TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*)audio;
+ DEBUG_TSMF("data_size %" PRIu32 "", data_size);
+
+ if (alsa->out_handle)
+ {
+ sbytes_per_frame = alsa->source_channels * alsa->bytes_per_sample;
+ rbytes_per_frame = alsa->actual_channels * alsa->bytes_per_sample;
+ pindex = src;
+ end = pindex + data_size;
+
+ while (pindex < end)
+ {
+ len = end - pindex;
+ frames = len / rbytes_per_frame;
+ error = snd_pcm_writei(alsa->out_handle, pindex, frames);
+
+ if (error == -EPIPE)
+ {
+ snd_pcm_recover(alsa->out_handle, error, 0);
+ error = 0;
+ }
+ else if (error < 0)
+ {
+ DEBUG_TSMF("error len %d", error);
+ snd_pcm_close(alsa->out_handle);
+ alsa->out_handle = 0;
+ tsmf_alsa_open_device(alsa);
+ break;
+ }
+
+ DEBUG_TSMF("%d frames played.", error);
+
+ if (error == 0)
+ break;
+
+ pindex += error * rbytes_per_frame;
+ }
+ }
+
+ return TRUE;
+}
+
+static UINT64 tsmf_alsa_get_latency(ITSMFAudioDevice* audio)
+{
+ UINT64 latency = 0;
+ snd_pcm_sframes_t frames = 0;
+ TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*)audio;
+
+ if (alsa->out_handle && alsa->actual_rate > 0 &&
+ snd_pcm_delay(alsa->out_handle, &frames) == 0 && frames > 0)
+ {
+ latency = ((UINT64)frames) * 10000000LL / (UINT64)alsa->actual_rate;
+ }
+
+ return latency;
+}
+
+static BOOL tsmf_alsa_flush(ITSMFAudioDevice* audio)
+{
+ return TRUE;
+}
+
+static void tsmf_alsa_free(ITSMFAudioDevice* audio)
+{
+ TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*)audio;
+ DEBUG_TSMF("");
+
+ if (alsa->out_handle)
+ {
+ snd_pcm_drain(alsa->out_handle);
+ snd_pcm_close(alsa->out_handle);
+ }
+
+ free(alsa);
+}
+
+FREERDP_ENTRY_POINT(ITSMFAudioDevice* alsa_freerdp_tsmf_client_audio_subsystem_entry(void))
+{
+ TSMFAlsaAudioDevice* alsa = calloc(1, sizeof(TSMFAlsaAudioDevice));
+ if (!alsa)
+ return NULL;
+
+ alsa->iface.Open = tsmf_alsa_open;
+ alsa->iface.SetFormat = tsmf_alsa_set_format;
+ alsa->iface.Play = tsmf_alsa_play;
+ alsa->iface.GetLatency = tsmf_alsa_get_latency;
+ alsa->iface.Flush = tsmf_alsa_flush;
+ alsa->iface.Free = tsmf_alsa_free;
+ return &alsa->iface;
+}
diff --git a/channels/tsmf/client/ffmpeg/CMakeLists.txt b/channels/tsmf/client/ffmpeg/CMakeLists.txt
new file mode 100644
index 0000000..a50bed0
--- /dev/null
+++ b/channels/tsmf/client/ffmpeg/CMakeLists.txt
@@ -0,0 +1,42 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("tsmf" "ffmpeg" "decoder")
+
+set(${MODULE_PREFIX}_SRCS
+ tsmf_ffmpeg.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+ ${FFMPEG_LIBRARIES}
+)
+if(APPLE)
+ # For this to work on apple, we need to add some frameworks
+ FIND_LIBRARY(COREFOUNDATION_LIBRARY CoreFoundation)
+ FIND_LIBRARY(COREVIDEO_LIBRARY CoreVideo)
+ FIND_LIBRARY(COREVIDEODECODE_LIBRARY VideoDecodeAcceleration)
+
+ list(APPEND ${MODULE_PREFIX}_LIBS ${COREFOUNDATION_LIBRARY} ${COREVIDEO_LIBRARY} ${COREVIDEODECODE_LIBRARY})
+endif()
+
+include_directories(..)
+include_directories(${FFMPEG_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
+
+
diff --git a/channels/tsmf/client/ffmpeg/tsmf_ffmpeg.c b/channels/tsmf/client/ffmpeg/tsmf_ffmpeg.c
new file mode 100644
index 0000000..eb45a8e
--- /dev/null
+++ b/channels/tsmf/client/ffmpeg/tsmf_ffmpeg.c
@@ -0,0 +1,695 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - FFmpeg Decoder
+ *
+ * Copyright 2010-2011 Vic Lee
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+
+#include <freerdp/channels/log.h>
+#include <freerdp/client/tsmf.h>
+
+#include <libavcodec/avcodec.h>
+#include <libavutil/common.h>
+#include <libavutil/cpu.h>
+#include <libavutil/imgutils.h>
+
+#include "tsmf_constants.h"
+#include "tsmf_decoder.h"
+
+/* Compatibility with older FFmpeg */
+#if LIBAVUTIL_VERSION_MAJOR < 50
+#define AVMEDIA_TYPE_VIDEO 0
+#define AVMEDIA_TYPE_AUDIO 1
+#endif
+
+#if LIBAVCODEC_VERSION_MAJOR < 54
+#define MAX_AUDIO_FRAME_SIZE AVCODEC_MAX_AUDIO_FRAME_SIZE
+#else
+#define MAX_AUDIO_FRAME_SIZE 192000
+#endif
+
+#if LIBAVCODEC_VERSION_MAJOR < 55
+#define AV_CODEC_ID_VC1 CODEC_ID_VC1
+#define AV_CODEC_ID_WMAV2 CODEC_ID_WMAV2
+#define AV_CODEC_ID_WMAPRO CODEC_ID_WMAPRO
+#define AV_CODEC_ID_MP3 CODEC_ID_MP3
+#define AV_CODEC_ID_MP2 CODEC_ID_MP2
+#define AV_CODEC_ID_MPEG2VIDEO CODEC_ID_MPEG2VIDEO
+#define AV_CODEC_ID_WMV3 CODEC_ID_WMV3
+#define AV_CODEC_ID_AAC CODEC_ID_AAC
+#define AV_CODEC_ID_H264 CODEC_ID_H264
+#define AV_CODEC_ID_AC3 CODEC_ID_AC3
+#endif
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56, 34, 2)
+#define AV_CODEC_CAP_TRUNCATED CODEC_CAP_TRUNCATED
+#define AV_CODEC_FLAG_TRUNCATED CODEC_FLAG_TRUNCATED
+#endif
+
+#if LIBAVUTIL_VERSION_MAJOR < 52
+#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P
+#endif
+
+typedef struct
+{
+ ITSMFDecoder iface;
+
+ int media_type;
+#if LIBAVCODEC_VERSION_MAJOR < 55
+ enum CodecID codec_id;
+#else
+ enum AVCodecID codec_id;
+#endif
+ AVCodecContext* codec_context;
+ AVCodec* codec;
+ AVFrame* frame;
+ int prepared;
+
+ BYTE* decoded_data;
+ UINT32 decoded_size;
+ UINT32 decoded_size_max;
+} TSMFFFmpegDecoder;
+
+static BOOL tsmf_ffmpeg_init_context(ITSMFDecoder* decoder)
+{
+ TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder;
+ mdecoder->codec_context = avcodec_alloc_context3(NULL);
+
+ if (!mdecoder->codec_context)
+ {
+ WLog_ERR(TAG, "avcodec_alloc_context failed.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL tsmf_ffmpeg_init_video_stream(ITSMFDecoder* decoder, const TS_AM_MEDIA_TYPE* media_type)
+{
+ TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder;
+ mdecoder->codec_context->width = media_type->Width;
+ mdecoder->codec_context->height = media_type->Height;
+ mdecoder->codec_context->bit_rate = media_type->BitRate;
+ mdecoder->codec_context->time_base.den = media_type->SamplesPerSecond.Numerator;
+ mdecoder->codec_context->time_base.num = media_type->SamplesPerSecond.Denominator;
+#if LIBAVCODEC_VERSION_MAJOR < 55
+ mdecoder->frame = avcodec_alloc_frame();
+#else
+ mdecoder->frame = av_frame_alloc();
+#endif
+ return TRUE;
+}
+
+static BOOL tsmf_ffmpeg_init_audio_stream(ITSMFDecoder* decoder, const TS_AM_MEDIA_TYPE* media_type)
+{
+ TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder;
+ mdecoder->codec_context->sample_rate = media_type->SamplesPerSecond.Numerator;
+ mdecoder->codec_context->bit_rate = media_type->BitRate;
+ mdecoder->codec_context->channels = media_type->Channels;
+ mdecoder->codec_context->block_align = media_type->BlockAlign;
+#if LIBAVCODEC_VERSION_MAJOR < 55
+#ifdef AV_CPU_FLAG_SSE2
+ mdecoder->codec_context->dsp_mask = AV_CPU_FLAG_SSE2 | AV_CPU_FLAG_MMX2;
+#else
+#if LIBAVCODEC_VERSION_MAJOR < 53
+ mdecoder->codec_context->dsp_mask = FF_MM_SSE2 | FF_MM_MMXEXT;
+#else
+ mdecoder->codec_context->dsp_mask = FF_MM_SSE2 | FF_MM_MMX2;
+#endif
+#endif
+#else /* LIBAVCODEC_VERSION_MAJOR < 55 */
+#ifdef AV_CPU_FLAG_SSE2
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 17, 100)
+ av_set_cpu_flags_mask(AV_CPU_FLAG_SSE2 | AV_CPU_FLAG_MMXEXT);
+#else
+ av_force_cpu_flags(AV_CPU_FLAG_SSE2 | AV_CPU_FLAG_MMXEXT);
+#endif
+#else
+ av_set_cpu_flags_mask(FF_MM_SSE2 | FF_MM_MMX2);
+#endif
+#endif /* LIBAVCODEC_VERSION_MAJOR < 55 */
+ return TRUE;
+}
+
+static BOOL tsmf_ffmpeg_init_stream(ITSMFDecoder* decoder, const TS_AM_MEDIA_TYPE* media_type)
+{
+ BYTE* p = NULL;
+ UINT32 size = 0;
+ const BYTE* s = NULL;
+ TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder;
+ mdecoder->codec = avcodec_find_decoder(mdecoder->codec_id);
+
+ if (!mdecoder->codec)
+ {
+ WLog_ERR(TAG, "avcodec_find_decoder failed.");
+ return FALSE;
+ }
+
+ mdecoder->codec_context->codec_id = mdecoder->codec_id;
+ mdecoder->codec_context->codec_type = mdecoder->media_type;
+
+ switch (mdecoder->media_type)
+ {
+ case AVMEDIA_TYPE_VIDEO:
+ if (!tsmf_ffmpeg_init_video_stream(decoder, media_type))
+ return FALSE;
+
+ break;
+
+ case AVMEDIA_TYPE_AUDIO:
+ if (!tsmf_ffmpeg_init_audio_stream(decoder, media_type))
+ return FALSE;
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "unknown media_type %d", mdecoder->media_type);
+ break;
+ }
+
+ if (media_type->ExtraData)
+ {
+ /* Add a padding to avoid invalid memory read in some codec */
+ mdecoder->codec_context->extradata_size = media_type->ExtraDataSize + 8;
+ mdecoder->codec_context->extradata = calloc(1, mdecoder->codec_context->extradata_size);
+
+ if (!mdecoder->codec_context->extradata)
+ return FALSE;
+
+ if (media_type->SubType == TSMF_SUB_TYPE_AVC1 &&
+ media_type->FormatType == TSMF_FORMAT_TYPE_MPEG2VIDEOINFO)
+ {
+ size_t required = 6;
+ /* The extradata format that FFmpeg uses is following CodecPrivate in Matroska.
+ See http://haali.su/mkv/codecs.pdf */
+ p = mdecoder->codec_context->extradata;
+ if ((mdecoder->codec_context->extradata_size < 0) ||
+ ((size_t)mdecoder->codec_context->extradata_size < required))
+ return FALSE;
+ *p++ = 1; /* Reserved? */
+ *p++ = media_type->ExtraData[8]; /* Profile */
+ *p++ = 0; /* Profile */
+ *p++ = media_type->ExtraData[12]; /* Level */
+ *p++ = 0xff; /* Flag? */
+ *p++ = 0xe0 | 0x01; /* Reserved | #sps */
+ s = media_type->ExtraData + 20;
+ size = ((UINT32)(*s)) * 256 + ((UINT32)(*(s + 1)));
+ required += size + 2;
+ if ((mdecoder->codec_context->extradata_size < 0) ||
+ ((size_t)mdecoder->codec_context->extradata_size < required))
+ return FALSE;
+ memcpy(p, s, size + 2);
+ s += size + 2;
+ p += size + 2;
+ required++;
+ if ((mdecoder->codec_context->extradata_size < 0) ||
+ ((size_t)mdecoder->codec_context->extradata_size < required))
+ return FALSE;
+ *p++ = 1; /* #pps */
+ size = ((UINT32)(*s)) * 256 + ((UINT32)(*(s + 1)));
+ required += size + 2;
+ if ((mdecoder->codec_context->extradata_size < 0) ||
+ ((size_t)mdecoder->codec_context->extradata_size < required))
+ return FALSE;
+ memcpy(p, s, size + 2);
+ }
+ else
+ {
+ memcpy(mdecoder->codec_context->extradata, media_type->ExtraData,
+ media_type->ExtraDataSize);
+ if ((mdecoder->codec_context->extradata_size < 0) ||
+ ((size_t)mdecoder->codec_context->extradata_size <
+ media_type->ExtraDataSize + 8ull))
+ return FALSE;
+ memset(mdecoder->codec_context->extradata + media_type->ExtraDataSize, 0, 8);
+ }
+ }
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(59, 18, 100)
+ if (mdecoder->codec->capabilities & AV_CODEC_CAP_TRUNCATED)
+ mdecoder->codec_context->flags |= AV_CODEC_FLAG_TRUNCATED;
+#endif
+
+ return TRUE;
+}
+
+static BOOL tsmf_ffmpeg_prepare(ITSMFDecoder* decoder)
+{
+ TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder;
+
+ if (avcodec_open2(mdecoder->codec_context, mdecoder->codec, NULL) < 0)
+ {
+ WLog_ERR(TAG, "avcodec_open2 failed.");
+ return FALSE;
+ }
+
+ mdecoder->prepared = 1;
+ return TRUE;
+}
+
+static BOOL tsmf_ffmpeg_set_format(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type)
+{
+ TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder;
+
+ WINPR_ASSERT(mdecoder);
+ WINPR_ASSERT(media_type);
+
+ switch (media_type->MajorType)
+ {
+ case TSMF_MAJOR_TYPE_VIDEO:
+ mdecoder->media_type = AVMEDIA_TYPE_VIDEO;
+ break;
+
+ case TSMF_MAJOR_TYPE_AUDIO:
+ mdecoder->media_type = AVMEDIA_TYPE_AUDIO;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ switch (media_type->SubType)
+ {
+ case TSMF_SUB_TYPE_WVC1:
+ mdecoder->codec_id = AV_CODEC_ID_VC1;
+ break;
+
+ case TSMF_SUB_TYPE_WMA2:
+ mdecoder->codec_id = AV_CODEC_ID_WMAV2;
+ break;
+
+ case TSMF_SUB_TYPE_WMA9:
+ mdecoder->codec_id = AV_CODEC_ID_WMAPRO;
+ break;
+
+ case TSMF_SUB_TYPE_MP3:
+ mdecoder->codec_id = AV_CODEC_ID_MP3;
+ break;
+
+ case TSMF_SUB_TYPE_MP2A:
+ mdecoder->codec_id = AV_CODEC_ID_MP2;
+ break;
+
+ case TSMF_SUB_TYPE_MP2V:
+ mdecoder->codec_id = AV_CODEC_ID_MPEG2VIDEO;
+ break;
+
+ case TSMF_SUB_TYPE_WMV3:
+ mdecoder->codec_id = AV_CODEC_ID_WMV3;
+ break;
+
+ case TSMF_SUB_TYPE_AAC:
+ mdecoder->codec_id = AV_CODEC_ID_AAC;
+
+ /* For AAC the pFormat is a HEAACWAVEINFO struct, and the codec data
+ is at the end of it. See
+ http://msdn.microsoft.com/en-us/library/dd757806.aspx */
+ if (media_type->ExtraData)
+ {
+ if (media_type->ExtraDataSize < 12)
+ return FALSE;
+
+ media_type->ExtraData += 12;
+ media_type->ExtraDataSize -= 12;
+ }
+
+ break;
+
+ case TSMF_SUB_TYPE_H264:
+ case TSMF_SUB_TYPE_AVC1:
+ mdecoder->codec_id = AV_CODEC_ID_H264;
+ break;
+
+ case TSMF_SUB_TYPE_AC3:
+ mdecoder->codec_id = AV_CODEC_ID_AC3;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ if (!tsmf_ffmpeg_init_context(decoder))
+ return FALSE;
+
+ if (!tsmf_ffmpeg_init_stream(decoder, media_type))
+ return FALSE;
+
+ if (!tsmf_ffmpeg_prepare(decoder))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL tsmf_ffmpeg_decode_video(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size,
+ UINT32 extensions)
+{
+ TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder;
+ int decoded = 0;
+ int len = 0;
+ AVFrame* frame = NULL;
+ BOOL ret = TRUE;
+#if LIBAVCODEC_VERSION_MAJOR < 52 || \
+ (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR <= 20)
+ len = avcodec_decode_video(mdecoder->codec_context, mdecoder->frame, &decoded, data, data_size);
+#else
+ {
+ AVPacket pkt;
+ av_init_packet(&pkt);
+ pkt.data = (BYTE*)data;
+ pkt.size = data_size;
+
+ if (extensions & TSMM_SAMPLE_EXT_CLEANPOINT)
+ pkt.flags |= AV_PKT_FLAG_KEY;
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 48, 101)
+ len = avcodec_decode_video2(mdecoder->codec_context, mdecoder->frame, &decoded, &pkt);
+#else
+ len = avcodec_send_packet(mdecoder->codec_context, &pkt);
+ if (len > 0)
+ {
+ do
+ {
+ len = avcodec_receive_frame(mdecoder->codec_context, mdecoder->frame);
+ } while (len == AVERROR(EAGAIN));
+ }
+#endif
+ }
+#endif
+
+ if (len < 0)
+ {
+ WLog_ERR(TAG, "data_size %" PRIu32 ", avcodec_decode_video failed (%d)", data_size, len);
+ ret = FALSE;
+ }
+ else if (!decoded)
+ {
+ WLog_ERR(TAG, "data_size %" PRIu32 ", no frame is decoded.", data_size);
+ ret = FALSE;
+ }
+ else
+ {
+ DEBUG_TSMF("linesize[0] %d linesize[1] %d linesize[2] %d linesize[3] %d "
+ "pix_fmt %d width %d height %d",
+ mdecoder->frame->linesize[0], mdecoder->frame->linesize[1],
+ mdecoder->frame->linesize[2], mdecoder->frame->linesize[3],
+ mdecoder->codec_context->pix_fmt, mdecoder->codec_context->width,
+ mdecoder->codec_context->height);
+ mdecoder->decoded_size = av_image_get_buffer_size(mdecoder->codec_context->pix_fmt,
+ mdecoder->codec_context->width,
+ mdecoder->codec_context->height, 1);
+ mdecoder->decoded_data = calloc(1, mdecoder->decoded_size);
+
+ if (!mdecoder->decoded_data)
+ return FALSE;
+
+#if LIBAVCODEC_VERSION_MAJOR < 55
+ frame = avcodec_alloc_frame();
+#else
+ frame = av_frame_alloc();
+#endif
+ av_image_fill_arrays(frame->data, frame->linesize, mdecoder->decoded_data,
+ mdecoder->codec_context->pix_fmt, mdecoder->codec_context->width,
+ mdecoder->codec_context->height, 1);
+ av_image_copy(frame->data, frame->linesize, (const uint8_t**)mdecoder->frame->data,
+ mdecoder->frame->linesize, mdecoder->codec_context->pix_fmt,
+ mdecoder->codec_context->width, mdecoder->codec_context->height);
+ av_free(frame);
+ }
+
+ return ret;
+}
+
+static BOOL tsmf_ffmpeg_decode_audio(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size,
+ UINT32 extensions)
+{
+ TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder;
+ int len = 0;
+ int frame_size = 0;
+ UINT32 src_size = 0;
+ const BYTE* src = NULL;
+ BYTE* dst = NULL;
+ int dst_offset = 0;
+#if 0
+ WLog_DBG(TAG, ("tsmf_ffmpeg_decode_audio: data_size %"PRIu32"", data_size));
+
+ for (int i = 0; i < data_size; i++)
+ {
+ WLog_DBG(TAG, ("%02"PRIX8"", data[i]));
+
+ if (i % 16 == 15)
+ WLog_DBG(TAG, ("\n"));
+ }
+
+#endif
+
+ if (mdecoder->decoded_size_max == 0)
+ mdecoder->decoded_size_max = MAX_AUDIO_FRAME_SIZE + 16;
+
+ mdecoder->decoded_data = calloc(1, mdecoder->decoded_size_max);
+
+ if (!mdecoder->decoded_data)
+ return FALSE;
+
+ /* align the memory for SSE2 needs */
+ dst = (BYTE*)(((uintptr_t)mdecoder->decoded_data + 15) & ~0x0F);
+ dst_offset = dst - mdecoder->decoded_data;
+ src = data;
+ src_size = data_size;
+
+ while (src_size > 0)
+ {
+ /* Ensure enough space for decoding */
+ if (mdecoder->decoded_size_max - mdecoder->decoded_size < MAX_AUDIO_FRAME_SIZE)
+ {
+ BYTE* tmp_data = NULL;
+ tmp_data = realloc(mdecoder->decoded_data, mdecoder->decoded_size_max * 2 + 16);
+
+ if (!tmp_data)
+ return FALSE;
+
+ mdecoder->decoded_size_max = mdecoder->decoded_size_max * 2 + 16;
+ mdecoder->decoded_data = tmp_data;
+ dst = (BYTE*)(((uintptr_t)mdecoder->decoded_data + 15) & ~0x0F);
+
+ if (dst - mdecoder->decoded_data != dst_offset)
+ {
+ /* re-align the memory if the alignment has changed after realloc */
+ memmove(dst, mdecoder->decoded_data + dst_offset, mdecoder->decoded_size);
+ dst_offset = dst - mdecoder->decoded_data;
+ }
+
+ dst += mdecoder->decoded_size;
+ }
+
+ frame_size = mdecoder->decoded_size_max - mdecoder->decoded_size;
+#if LIBAVCODEC_VERSION_MAJOR < 52 || \
+ (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR <= 20)
+ len = avcodec_decode_audio2(mdecoder->codec_context, (int16_t*)dst, &frame_size, src,
+ src_size);
+#else
+ {
+#if LIBAVCODEC_VERSION_MAJOR < 55
+ AVFrame* decoded_frame = avcodec_alloc_frame();
+#else
+ AVFrame* decoded_frame = av_frame_alloc();
+#endif
+ int got_frame = 0;
+ AVPacket pkt;
+ av_init_packet(&pkt);
+ pkt.data = (BYTE*)src;
+ pkt.size = src_size;
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 48, 101)
+ len = avcodec_decode_audio4(mdecoder->codec_context, decoded_frame, &got_frame, &pkt);
+#else
+ len = avcodec_send_packet(mdecoder->codec_context, &pkt);
+ if (len > 0)
+ {
+ do
+ {
+ len = avcodec_receive_frame(mdecoder->codec_context, decoded_frame);
+ } while (len == AVERROR(EAGAIN));
+ }
+#endif
+
+ if (len >= 0 && got_frame)
+ {
+ frame_size = av_samples_get_buffer_size(NULL, mdecoder->codec_context->channels,
+ decoded_frame->nb_samples,
+ mdecoder->codec_context->sample_fmt, 1);
+ memcpy(dst, decoded_frame->data[0], frame_size);
+ }
+ else
+ {
+ frame_size = 0;
+ }
+
+ av_free(decoded_frame);
+ }
+#endif
+
+ if (len > 0)
+ {
+ src += len;
+ src_size -= len;
+ }
+
+ if (frame_size > 0)
+ {
+ mdecoder->decoded_size += frame_size;
+ dst += frame_size;
+ }
+ }
+
+ if (mdecoder->decoded_size == 0)
+ {
+ free(mdecoder->decoded_data);
+ mdecoder->decoded_data = NULL;
+ }
+ else if (dst_offset)
+ {
+ /* move the aligned decoded data to original place */
+ memmove(mdecoder->decoded_data, mdecoder->decoded_data + dst_offset,
+ mdecoder->decoded_size);
+ }
+
+ DEBUG_TSMF("data_size %" PRIu32 " decoded_size %" PRIu32 "", data_size, mdecoder->decoded_size);
+ return TRUE;
+}
+
+static BOOL tsmf_ffmpeg_decode(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size,
+ UINT32 extensions)
+{
+ TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder;
+
+ if (mdecoder->decoded_data)
+ {
+ free(mdecoder->decoded_data);
+ mdecoder->decoded_data = NULL;
+ }
+
+ mdecoder->decoded_size = 0;
+
+ switch (mdecoder->media_type)
+ {
+ case AVMEDIA_TYPE_VIDEO:
+ return tsmf_ffmpeg_decode_video(decoder, data, data_size, extensions);
+
+ case AVMEDIA_TYPE_AUDIO:
+ return tsmf_ffmpeg_decode_audio(decoder, data, data_size, extensions);
+
+ default:
+ WLog_ERR(TAG, "unknown media type.");
+ return FALSE;
+ }
+}
+
+static BYTE* tsmf_ffmpeg_get_decoded_data(ITSMFDecoder* decoder, UINT32* size)
+{
+ BYTE* buf = NULL;
+ TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder;
+ *size = mdecoder->decoded_size;
+ buf = mdecoder->decoded_data;
+ mdecoder->decoded_data = NULL;
+ mdecoder->decoded_size = 0;
+ return buf;
+}
+
+static UINT32 tsmf_ffmpeg_get_decoded_format(ITSMFDecoder* decoder)
+{
+ TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder;
+
+ switch (mdecoder->codec_context->pix_fmt)
+ {
+ case AV_PIX_FMT_YUV420P:
+ return RDP_PIXFMT_I420;
+
+ default:
+ WLog_ERR(TAG, "unsupported pixel format %u", mdecoder->codec_context->pix_fmt);
+ return (UINT32)-1;
+ }
+}
+
+static BOOL tsmf_ffmpeg_get_decoded_dimension(ITSMFDecoder* decoder, UINT32* width, UINT32* height)
+{
+ TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder;
+
+ if (mdecoder->codec_context->width > 0 && mdecoder->codec_context->height > 0)
+ {
+ *width = mdecoder->codec_context->width;
+ *height = mdecoder->codec_context->height;
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+static void tsmf_ffmpeg_free(ITSMFDecoder* decoder)
+{
+ TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder;
+
+ if (mdecoder->frame)
+ av_free(mdecoder->frame);
+
+ free(mdecoder->decoded_data);
+
+ if (mdecoder->codec_context)
+ {
+ if (mdecoder->prepared)
+ avcodec_close(mdecoder->codec_context);
+
+ free(mdecoder->codec_context->extradata);
+ av_free(mdecoder->codec_context);
+ }
+
+ free(decoder);
+}
+
+static INIT_ONCE g_Initialized = INIT_ONCE_STATIC_INIT;
+static BOOL CALLBACK InitializeAvCodecs(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)
+ avcodec_register_all();
+#endif
+ return TRUE;
+}
+
+FREERDP_ENTRY_POINT(ITSMFDecoder* ffmpeg_freerdp_tsmf_client_decoder_subsystem_entry(void))
+{
+ TSMFFFmpegDecoder* decoder = NULL;
+ InitOnceExecuteOnce(&g_Initialized, InitializeAvCodecs, NULL, NULL);
+ WLog_DBG(TAG, "TSMFDecoderEntry FFMPEG");
+ decoder = (TSMFFFmpegDecoder*)calloc(1, sizeof(TSMFFFmpegDecoder));
+
+ if (!decoder)
+ return NULL;
+
+ decoder->iface.SetFormat = tsmf_ffmpeg_set_format;
+ decoder->iface.Decode = tsmf_ffmpeg_decode;
+ decoder->iface.GetDecodedData = tsmf_ffmpeg_get_decoded_data;
+ decoder->iface.GetDecodedFormat = tsmf_ffmpeg_get_decoded_format;
+ decoder->iface.GetDecodedDimension = tsmf_ffmpeg_get_decoded_dimension;
+ decoder->iface.Free = tsmf_ffmpeg_free;
+ return (ITSMFDecoder*)decoder;
+}
diff --git a/channels/tsmf/client/gstreamer/CMakeLists.txt b/channels/tsmf/client/gstreamer/CMakeLists.txt
new file mode 100644
index 0000000..9490cc0
--- /dev/null
+++ b/channels/tsmf/client/gstreamer/CMakeLists.txt
@@ -0,0 +1,76 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script for gstreamer subsystem
+#
+# (C) Copyright 2012 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("tsmf" "gstreamer" "decoder")
+
+if(NOT gstreamer_FOUND)
+ message(FATAL_ERROR "GStreamer library not found, but required for TSMF module.")
+endif()
+
+set(SRC "tsmf_gstreamer.c")
+
+pkg_check_modules(gstreamerbase gstreamer-base-1.0 REQUIRED)
+pkg_check_modules(gstreamervideo gstreamer-video-1.0 REQUIRED)
+pkg_check_modules(gstreamerapp gstreamer-app-1.0 REQUIRED)
+
+set(LIBS
+ ${gstreamer_LIBRARIES}
+ ${gstreamerbase_LIBRARIES}
+ ${gstreamervideo_LIBRARIES}
+ ${gstreamerapp_LIBRARIES}
+)
+include_directories(
+ ${gstreamer_INCLUDE_DIRS}
+ ${gstreamerbase_INCLUDE_DIRS}
+ ${gstreamervideo_INCLUDE_DIRS}
+ ${gstreamerapp_INCLUDE_DIRS}
+)
+
+
+if(ANDROID)
+ set(SRC ${SRC}
+ tsmf_android.c)
+else()
+ find_package(X11 REQUIRED)
+
+ list(APPEND SRC
+ tsmf_X11.c)
+ list(APPEND LIBS
+ ${X11_LIBRARIES}
+ ${X11_Xext_LIB})
+ if (NOT APPLE)
+ list(APPEND LIBS rt)
+ endif()
+
+ if(X11_Xext_FOUND)
+ add_definitions(-DWITH_XEXT=1)
+ endif()
+
+endif()
+
+set(${MODULE_PREFIX}_SRCS
+ "${SRC}"
+)
+
+set(${MODULE_PREFIX}_LIBS
+ ${LIBS}
+ winpr
+)
+
+include_directories(..)
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/tsmf/client/gstreamer/tsmf_X11.c b/channels/tsmf/client/gstreamer/tsmf_X11.c
new file mode 100644
index 0000000..987e69b
--- /dev/null
+++ b/channels/tsmf/client/gstreamer/tsmf_X11.c
@@ -0,0 +1,500 @@
+/*
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - GStreamer Decoder X11 specifics
+ *
+ * (C) Copyright 2014 Thincast Technologies GmbH
+ * (C) Copyright 2014 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#ifndef __CYGWIN__
+#include <sys/syscall.h>
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <err.h>
+#include <errno.h>
+#include <winpr/thread.h>
+#include <winpr/string.h>
+#include <winpr/platform.h>
+
+#include <gst/gst.h>
+
+#if GST_VERSION_MAJOR > 0
+#include <gst/video/videooverlay.h>
+#else
+#include <gst/interfaces/xoverlay.h>
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/extensions/shape.h>
+
+#include <freerdp/channels/tsmf.h>
+
+#include "tsmf_platform.h"
+#include "tsmf_constants.h"
+#include "tsmf_decoder.h"
+
+#if !defined(WITH_XEXT)
+#warning "Building TSMF without shape extension support"
+#endif
+
+struct X11Handle
+{
+ int shmid;
+ int* xfwin;
+#if defined(WITH_XEXT)
+ BOOL has_shape;
+#endif
+ Display* disp;
+ Window subwin;
+ BOOL subwinMapped;
+#if GST_VERSION_MAJOR > 0
+ GstVideoOverlay* overlay;
+#else
+ GstXOverlay* overlay;
+#endif
+ int subwinWidth;
+ int subwinHeight;
+ int subwinX;
+ int subwinY;
+};
+
+static const char* get_shm_id()
+{
+ static char shm_id[128];
+ sprintf_s(shm_id, sizeof(shm_id), "/com.freerdp.xfreerdp.tsmf_%016X", GetCurrentProcessId());
+ return shm_id;
+}
+
+static GstBusSyncReply tsmf_platform_bus_sync_handler(GstBus* bus, GstMessage* message,
+ gpointer user_data)
+{
+ struct X11Handle* hdl;
+
+ TSMFGstreamerDecoder* decoder = user_data;
+
+ if (GST_MESSAGE_TYPE(message) != GST_MESSAGE_ELEMENT)
+ return GST_BUS_PASS;
+
+#if GST_VERSION_MAJOR > 0
+ if (!gst_is_video_overlay_prepare_window_handle_message(message))
+ return GST_BUS_PASS;
+#else
+ if (!gst_structure_has_name(message->structure, "prepare-xwindow-id"))
+ return GST_BUS_PASS;
+#endif
+
+ hdl = (struct X11Handle*)decoder->platform;
+
+ if (hdl->subwin)
+ {
+#if GST_VERSION_MAJOR > 0
+ hdl->overlay = GST_VIDEO_OVERLAY(GST_MESSAGE_SRC(message));
+ gst_video_overlay_set_window_handle(hdl->overlay, hdl->subwin);
+ gst_video_overlay_handle_events(hdl->overlay, FALSE);
+#else
+ hdl->overlay = GST_X_OVERLAY(GST_MESSAGE_SRC(message));
+#if GST_CHECK_VERSION(0, 10, 31)
+ gst_x_overlay_set_window_handle(hdl->overlay, hdl->subwin);
+#else
+ gst_x_overlay_set_xwindow_id(hdl->overlay, hdl->subwin);
+#endif
+ gst_x_overlay_handle_events(hdl->overlay, TRUE);
+#endif
+
+ if (hdl->subwinWidth != -1 && hdl->subwinHeight != -1 && hdl->subwinX != -1 &&
+ hdl->subwinY != -1)
+ {
+#if GST_VERSION_MAJOR > 0
+ if (!gst_video_overlay_set_render_rectangle(hdl->overlay, 0, 0, hdl->subwinWidth,
+ hdl->subwinHeight))
+ {
+ WLog_ERR(TAG, "Could not resize overlay!");
+ }
+
+ gst_video_overlay_expose(hdl->overlay);
+#else
+ if (!gst_x_overlay_set_render_rectangle(hdl->overlay, 0, 0, hdl->subwinWidth,
+ hdl->subwinHeight))
+ {
+ WLog_ERR(TAG, "Could not resize overlay!");
+ }
+
+ gst_x_overlay_expose(hdl->overlay);
+#endif
+ XLockDisplay(hdl->disp);
+ XMoveResizeWindow(hdl->disp, hdl->subwin, hdl->subwinX, hdl->subwinY, hdl->subwinWidth,
+ hdl->subwinHeight);
+ XSync(hdl->disp, FALSE);
+ XUnlockDisplay(hdl->disp);
+ }
+ }
+ else
+ {
+ g_warning("Window was not available before retrieving the overlay!");
+ }
+
+ gst_message_unref(message);
+
+ return GST_BUS_DROP;
+}
+
+const char* tsmf_platform_get_video_sink(void)
+{
+ return "autovideosink";
+}
+
+const char* tsmf_platform_get_audio_sink(void)
+{
+ return "autoaudiosink";
+}
+
+int tsmf_platform_create(TSMFGstreamerDecoder* decoder)
+{
+ struct X11Handle* hdl;
+
+ if (!decoder)
+ return -1;
+
+ if (decoder->platform)
+ return -1;
+
+ hdl = calloc(1, sizeof(struct X11Handle));
+ if (!hdl)
+ {
+ WLog_ERR(TAG, "Could not allocate handle.");
+ return -1;
+ }
+
+ decoder->platform = hdl;
+ hdl->shmid = shm_open(get_shm_id(), (O_RDWR | O_CREAT), (PROT_READ | PROT_WRITE));
+ if (hdl->shmid == -1)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "failed to get access to shared memory - shmget(%s): %i - %s", get_shm_id(),
+ errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ return -2;
+ }
+
+ hdl->xfwin = mmap(0, sizeof(void*), PROT_READ | PROT_WRITE, MAP_SHARED, hdl->shmid, 0);
+ if (hdl->xfwin == MAP_FAILED)
+ {
+ WLog_ERR(TAG, "shmat failed!");
+ return -3;
+ }
+
+ hdl->disp = XOpenDisplay(NULL);
+ if (!hdl->disp)
+ {
+ WLog_ERR(TAG, "Failed to open display");
+ return -4;
+ }
+
+ hdl->subwinMapped = FALSE;
+ hdl->subwinX = -1;
+ hdl->subwinY = -1;
+ hdl->subwinWidth = -1;
+ hdl->subwinHeight = -1;
+
+ return 0;
+}
+
+int tsmf_platform_set_format(TSMFGstreamerDecoder* decoder)
+{
+ if (!decoder)
+ return -1;
+
+ if (decoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
+ {
+ }
+
+ return 0;
+}
+
+int tsmf_platform_register_handler(TSMFGstreamerDecoder* decoder)
+{
+ GstBus* bus;
+
+ if (!decoder)
+ return -1;
+
+ if (!decoder->pipe)
+ return -1;
+
+ bus = gst_pipeline_get_bus(GST_PIPELINE(decoder->pipe));
+
+#if GST_VERSION_MAJOR > 0
+ gst_bus_set_sync_handler(bus, (GstBusSyncHandler)tsmf_platform_bus_sync_handler, decoder, NULL);
+#else
+ gst_bus_set_sync_handler(bus, (GstBusSyncHandler)tsmf_platform_bus_sync_handler, decoder);
+#endif
+
+ if (!bus)
+ {
+ WLog_ERR(TAG, "gst_pipeline_get_bus failed!");
+ return 1;
+ }
+
+ gst_object_unref(bus);
+
+ return 0;
+}
+
+int tsmf_platform_free(TSMFGstreamerDecoder* decoder)
+{
+ struct X11Handle* hdl = decoder->platform;
+
+ if (!hdl)
+ return -1;
+
+ if (hdl->disp)
+ XCloseDisplay(hdl->disp);
+
+ if (hdl->xfwin)
+ munmap(0, sizeof(void*));
+
+ if (hdl->shmid >= 0)
+ close(hdl->shmid);
+
+ free(hdl);
+ decoder->platform = NULL;
+
+ return 0;
+}
+
+int tsmf_window_create(TSMFGstreamerDecoder* decoder)
+{
+ struct X11Handle* hdl;
+
+ if (decoder->media_type != TSMF_MAJOR_TYPE_VIDEO)
+ {
+ decoder->ready = TRUE;
+ return -3;
+ }
+ else
+ {
+ if (!decoder)
+ return -1;
+
+ if (!decoder->platform)
+ return -1;
+
+ hdl = (struct X11Handle*)decoder->platform;
+
+ if (!hdl->subwin)
+ {
+ XLockDisplay(hdl->disp);
+ hdl->subwin = XCreateSimpleWindow(hdl->disp, *(int*)hdl->xfwin, 0, 0, 1, 1, 0, 0, 0);
+ XUnlockDisplay(hdl->disp);
+
+ if (!hdl->subwin)
+ {
+ WLog_ERR(TAG, "Could not create subwindow!");
+ }
+ }
+
+ tsmf_window_map(decoder);
+
+ decoder->ready = TRUE;
+#if defined(WITH_XEXT)
+ int event, error;
+ XLockDisplay(hdl->disp);
+ hdl->has_shape = XShapeQueryExtension(hdl->disp, &event, &error);
+ XUnlockDisplay(hdl->disp);
+#endif
+ }
+
+ return 0;
+}
+
+int tsmf_window_resize(TSMFGstreamerDecoder* decoder, int x, int y, int width, int height,
+ int nr_rects, RDP_RECT* rects)
+{
+ struct X11Handle* hdl;
+
+ if (!decoder)
+ return -1;
+
+ if (decoder->media_type != TSMF_MAJOR_TYPE_VIDEO)
+ {
+ return -3;
+ }
+
+ if (!decoder->platform)
+ return -1;
+
+ hdl = (struct X11Handle*)decoder->platform;
+ DEBUG_TSMF("resize: x=%d, y=%d, w=%d, h=%d", x, y, width, height);
+
+ if (hdl->overlay)
+ {
+#if GST_VERSION_MAJOR > 0
+
+ if (!gst_video_overlay_set_render_rectangle(hdl->overlay, 0, 0, width, height))
+ {
+ WLog_ERR(TAG, "Could not resize overlay!");
+ }
+
+ gst_video_overlay_expose(hdl->overlay);
+#else
+ if (!gst_x_overlay_set_render_rectangle(hdl->overlay, 0, 0, width, height))
+ {
+ WLog_ERR(TAG, "Could not resize overlay!");
+ }
+
+ gst_x_overlay_expose(hdl->overlay);
+#endif
+ }
+
+ if (hdl->subwin)
+ {
+ hdl->subwinX = x;
+ hdl->subwinY = y;
+ hdl->subwinWidth = width;
+ hdl->subwinHeight = height;
+
+ XLockDisplay(hdl->disp);
+ XMoveResizeWindow(hdl->disp, hdl->subwin, hdl->subwinX, hdl->subwinY, hdl->subwinWidth,
+ hdl->subwinHeight);
+
+ /* Unmap the window if there are no visibility rects */
+ if (nr_rects == 0)
+ tsmf_window_unmap(decoder);
+ else
+ tsmf_window_map(decoder);
+
+#if defined(WITH_XEXT)
+ if (hdl->has_shape)
+ {
+ XRectangle* xrects = NULL;
+
+ if (nr_rects == 0)
+ {
+ xrects = calloc(1, sizeof(XRectangle));
+ xrects->x = x;
+ xrects->y = y;
+ xrects->width = width;
+ xrects->height = height;
+ }
+ else
+ {
+ xrects = calloc(nr_rects, sizeof(XRectangle));
+ }
+
+ if (xrects)
+ {
+ for (int i = 0; i < nr_rects; i++)
+ {
+ xrects[i].x = rects[i].x - x;
+ xrects[i].y = rects[i].y - y;
+ xrects[i].width = rects[i].width;
+ xrects[i].height = rects[i].height;
+ }
+
+ XShapeCombineRectangles(hdl->disp, hdl->subwin, ShapeBounding, x, y, xrects,
+ nr_rects, ShapeSet, 0);
+ free(xrects);
+ }
+ }
+#endif
+ XSync(hdl->disp, FALSE);
+ XUnlockDisplay(hdl->disp);
+ }
+
+ return 0;
+}
+
+int tsmf_window_map(TSMFGstreamerDecoder* decoder)
+{
+ struct X11Handle* hdl;
+ if (!decoder)
+ return -1;
+
+ hdl = (struct X11Handle*)decoder->platform;
+
+ /* Only need to map the window if it is not currently mapped */
+ if ((hdl->subwin) && (!hdl->subwinMapped))
+ {
+ XLockDisplay(hdl->disp);
+ XMapWindow(hdl->disp, hdl->subwin);
+ hdl->subwinMapped = TRUE;
+ XSync(hdl->disp, FALSE);
+ XUnlockDisplay(hdl->disp);
+ }
+
+ return 0;
+}
+
+int tsmf_window_unmap(TSMFGstreamerDecoder* decoder)
+{
+ struct X11Handle* hdl;
+ if (!decoder)
+ return -1;
+
+ hdl = (struct X11Handle*)decoder->platform;
+
+ /* only need to unmap window if it is currently mapped */
+ if ((hdl->subwin) && (hdl->subwinMapped))
+ {
+ XLockDisplay(hdl->disp);
+ XUnmapWindow(hdl->disp, hdl->subwin);
+ hdl->subwinMapped = FALSE;
+ XSync(hdl->disp, FALSE);
+ XUnlockDisplay(hdl->disp);
+ }
+
+ return 0;
+}
+
+int tsmf_window_destroy(TSMFGstreamerDecoder* decoder)
+{
+ struct X11Handle* hdl;
+
+ if (!decoder)
+ return -1;
+
+ decoder->ready = FALSE;
+
+ if (decoder->media_type != TSMF_MAJOR_TYPE_VIDEO)
+ return -3;
+
+ if (!decoder->platform)
+ return -1;
+
+ hdl = (struct X11Handle*)decoder->platform;
+
+ if (hdl->subwin)
+ {
+ XLockDisplay(hdl->disp);
+ XDestroyWindow(hdl->disp, hdl->subwin);
+ XSync(hdl->disp, FALSE);
+ XUnlockDisplay(hdl->disp);
+ }
+
+ hdl->overlay = NULL;
+ hdl->subwin = 0;
+ hdl->subwinMapped = FALSE;
+ hdl->subwinX = -1;
+ hdl->subwinY = -1;
+ hdl->subwinWidth = -1;
+ hdl->subwinHeight = -1;
+ return 0;
+}
diff --git a/channels/tsmf/client/gstreamer/tsmf_gstreamer.c b/channels/tsmf/client/gstreamer/tsmf_gstreamer.c
new file mode 100644
index 0000000..9087876
--- /dev/null
+++ b/channels/tsmf/client/gstreamer/tsmf_gstreamer.c
@@ -0,0 +1,1054 @@
+/*
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - GStreamer Decoder
+ *
+ * (C) Copyright 2012 HP Development Company, LLC
+ * (C) Copyright 2014 Thincast Technologies GmbH
+ * (C) Copyright 2014 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <winpr/string.h>
+#include <winpr/platform.h>
+
+#include <gst/gst.h>
+
+#include <gst/app/gstappsrc.h>
+#include <gst/app/gstappsink.h>
+
+#include "tsmf_constants.h"
+#include "tsmf_decoder.h"
+#include "tsmf_platform.h"
+
+/* 1 second = 10,000,000 100ns units*/
+#define SEEK_TOLERANCE 10 * 1000 * 1000
+
+static BOOL tsmf_gstreamer_pipeline_build(TSMFGstreamerDecoder* mdecoder);
+static void tsmf_gstreamer_clean_up(TSMFGstreamerDecoder* mdecoder);
+static int tsmf_gstreamer_pipeline_set_state(TSMFGstreamerDecoder* mdecoder,
+ GstState desired_state);
+static BOOL tsmf_gstreamer_buffer_level(ITSMFDecoder* decoder);
+
+static const char* get_type(TSMFGstreamerDecoder* mdecoder)
+{
+ if (!mdecoder)
+ return NULL;
+
+ switch (mdecoder->media_type)
+ {
+ case TSMF_MAJOR_TYPE_VIDEO:
+ return "VIDEO";
+ case TSMF_MAJOR_TYPE_AUDIO:
+ return "AUDIO";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static void cb_child_added(GstChildProxy* child_proxy, GObject* object,
+ TSMFGstreamerDecoder* mdecoder)
+{
+ DEBUG_TSMF("NAME: %s", G_OBJECT_TYPE_NAME(object));
+
+ if (!g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstXvImageSink") ||
+ !g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstXImageSink") ||
+ !g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstFluVAAutoSink"))
+ {
+ gst_base_sink_set_max_lateness((GstBaseSink*)object, 10000000); /* nanoseconds */
+ g_object_set(G_OBJECT(object), "sync", TRUE, NULL); /* synchronize on the clock */
+ g_object_set(G_OBJECT(object), "async", TRUE, NULL); /* no async state changes */
+ }
+
+ else if (!g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstAlsaSink") ||
+ !g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstPulseSink"))
+ {
+ gst_base_sink_set_max_lateness((GstBaseSink*)object, 10000000); /* nanoseconds */
+ g_object_set(G_OBJECT(object), "slave-method", 1, NULL);
+ g_object_set(G_OBJECT(object), "buffer-time", (gint64)20000, NULL); /* microseconds */
+ g_object_set(G_OBJECT(object), "drift-tolerance", (gint64)20000, NULL); /* microseconds */
+ g_object_set(G_OBJECT(object), "latency-time", (gint64)10000, NULL); /* microseconds */
+ g_object_set(G_OBJECT(object), "sync", TRUE, NULL); /* synchronize on the clock */
+ g_object_set(G_OBJECT(object), "async", TRUE, NULL); /* no async state changes */
+ }
+}
+
+static void tsmf_gstreamer_enough_data(GstAppSrc* src, gpointer user_data)
+{
+ TSMFGstreamerDecoder* mdecoder = user_data;
+ (void)mdecoder;
+ DEBUG_TSMF("%s", get_type(mdecoder));
+}
+
+static void tsmf_gstreamer_need_data(GstAppSrc* src, guint length, gpointer user_data)
+{
+ TSMFGstreamerDecoder* mdecoder = user_data;
+ (void)mdecoder;
+ DEBUG_TSMF("%s length=%u", get_type(mdecoder), length);
+}
+
+static gboolean tsmf_gstreamer_seek_data(GstAppSrc* src, guint64 offset, gpointer user_data)
+{
+ TSMFGstreamerDecoder* mdecoder = user_data;
+ (void)mdecoder;
+ DEBUG_TSMF("%s offset=%" PRIu64 "", get_type(mdecoder), offset);
+
+ return TRUE;
+}
+
+static BOOL tsmf_gstreamer_change_volume(ITSMFDecoder* decoder, UINT32 newVolume, UINT32 muted)
+{
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+
+ if (!mdecoder || !mdecoder->pipe)
+ return TRUE;
+
+ if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
+ return TRUE;
+
+ mdecoder->gstMuted = (BOOL)muted;
+ DEBUG_TSMF("mute=[%" PRId32 "]", mdecoder->gstMuted);
+ mdecoder->gstVolume = (double)newVolume / (double)10000;
+ DEBUG_TSMF("gst_new_vol=[%f]", mdecoder->gstVolume);
+
+ if (!mdecoder->volume)
+ return TRUE;
+
+ if (!G_IS_OBJECT(mdecoder->volume))
+ return TRUE;
+
+ g_object_set(mdecoder->volume, "mute", mdecoder->gstMuted, NULL);
+ g_object_set(mdecoder->volume, "volume", mdecoder->gstVolume, NULL);
+
+ return TRUE;
+}
+
+static inline GstClockTime tsmf_gstreamer_timestamp_ms_to_gst(UINT64 ms_timestamp)
+{
+ /*
+ * Convert Microsoft 100ns timestamps to Gstreamer 1ns units.
+ */
+ return (GstClockTime)(ms_timestamp * 100);
+}
+
+int tsmf_gstreamer_pipeline_set_state(TSMFGstreamerDecoder* mdecoder, GstState desired_state)
+{
+ GstStateChangeReturn state_change;
+ const char* name;
+ const char* sname = get_type(mdecoder);
+
+ if (!mdecoder)
+ return 0;
+
+ if (!mdecoder->pipe)
+ return 0; /* Just in case this is called during startup or shutdown when we don't expect it
+ */
+
+ if (desired_state == mdecoder->state)
+ return 0; /* Redundant request - Nothing to do */
+
+ name = gst_element_state_get_name(desired_state); /* For debug */
+ DEBUG_TSMF("%s to %s", sname, name);
+ state_change = gst_element_set_state(mdecoder->pipe, desired_state);
+
+ if (state_change == GST_STATE_CHANGE_FAILURE)
+ {
+ WLog_ERR(TAG, "%s: (%s) GST_STATE_CHANGE_FAILURE.", sname, name);
+ }
+ else if (state_change == GST_STATE_CHANGE_ASYNC)
+ {
+ WLog_ERR(TAG, "%s: (%s) GST_STATE_CHANGE_ASYNC.", sname, name);
+ mdecoder->state = desired_state;
+ }
+ else
+ {
+ mdecoder->state = desired_state;
+ }
+
+ return 0;
+}
+
+static GstBuffer* tsmf_get_buffer_from_data(const void* raw_data, gsize size)
+{
+ GstBuffer* buffer;
+ gpointer data;
+
+ if (!raw_data)
+ return NULL;
+
+ if (size < 1)
+ return NULL;
+
+ data = g_malloc(size);
+
+ if (!data)
+ {
+ WLog_ERR(TAG, "Could not allocate %" G_GSIZE_FORMAT " bytes of data.", size);
+ return NULL;
+ }
+
+ CopyMemory(data, raw_data, size);
+
+#if GST_VERSION_MAJOR > 0
+ buffer = gst_buffer_new_wrapped(data, size);
+#else
+ buffer = gst_buffer_new();
+
+ if (!buffer)
+ {
+ WLog_ERR(TAG, "Could not create GstBuffer");
+ free(data);
+ return NULL;
+ }
+
+ GST_BUFFER_MALLOCDATA(buffer) = data;
+ GST_BUFFER_SIZE(buffer) = size;
+ GST_BUFFER_DATA(buffer) = GST_BUFFER_MALLOCDATA(buffer);
+#endif
+
+ return buffer;
+}
+
+static BOOL tsmf_gstreamer_set_format(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type)
+{
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+
+ if (!mdecoder)
+ return FALSE;
+
+ DEBUG_TSMF("");
+
+ switch (media_type->MajorType)
+ {
+ case TSMF_MAJOR_TYPE_VIDEO:
+ mdecoder->media_type = TSMF_MAJOR_TYPE_VIDEO;
+ break;
+ case TSMF_MAJOR_TYPE_AUDIO:
+ mdecoder->media_type = TSMF_MAJOR_TYPE_AUDIO;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (media_type->SubType)
+ {
+ case TSMF_SUB_TYPE_WVC1:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-wmv", "bitrate", G_TYPE_UINT, media_type->BitRate, "width", G_TYPE_INT,
+ media_type->Width, "height", G_TYPE_INT, media_type->Height, "wmvversion",
+ G_TYPE_INT, 3,
+#if GST_VERSION_MAJOR > 0
+ "format", G_TYPE_STRING, "WVC1",
+#else
+ "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'V', 'C', '1'),
+#endif
+ "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
+ media_type->SamplesPerSecond.Denominator, "pixel-aspect-ratio", GST_TYPE_FRACTION,
+ 1, 1, NULL);
+ break;
+ case TSMF_SUB_TYPE_MP4S:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-divx", "divxversion", G_TYPE_INT, 5, "bitrate", G_TYPE_UINT,
+ media_type->BitRate, "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT,
+ media_type->Height,
+#if GST_VERSION_MAJOR > 0
+ "format", G_TYPE_STRING, "MP42",
+#else
+ "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', 'P', '4', '2'),
+#endif
+ "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
+ media_type->SamplesPerSecond.Denominator, NULL);
+ break;
+ case TSMF_SUB_TYPE_MP42:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-msmpeg", "msmpegversion", G_TYPE_INT, 42, "bitrate", G_TYPE_UINT,
+ media_type->BitRate, "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT,
+ media_type->Height,
+#if GST_VERSION_MAJOR > 0
+ "format", G_TYPE_STRING, "MP42",
+#else
+ "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', 'P', '4', '2'),
+#endif
+ "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
+ media_type->SamplesPerSecond.Denominator, NULL);
+ break;
+ case TSMF_SUB_TYPE_MP43:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-msmpeg", "msmpegversion", G_TYPE_INT, 43, "bitrate", G_TYPE_UINT,
+ media_type->BitRate, "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT,
+ media_type->Height,
+#if GST_VERSION_MAJOR > 0
+ "format", G_TYPE_STRING, "MP43",
+#else
+ "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', 'P', '4', '3'),
+#endif
+ "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
+ media_type->SamplesPerSecond.Denominator, NULL);
+ break;
+ case TSMF_SUB_TYPE_M4S2:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/mpeg", "mpegversion", G_TYPE_INT, 4, "width", G_TYPE_INT, media_type->Width,
+ "height", G_TYPE_INT, media_type->Height,
+#if GST_VERSION_MAJOR > 0
+ "format", G_TYPE_STRING, "M4S2",
+#else
+ "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', '4', 'S', '2'),
+#endif
+ "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
+ media_type->SamplesPerSecond.Denominator, NULL);
+ break;
+ case TSMF_SUB_TYPE_WMA9:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "audio/x-wma", "wmaversion", G_TYPE_INT, 3, "rate", G_TYPE_INT,
+ media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT,
+ media_type->Channels, "bitrate", G_TYPE_INT, media_type->BitRate, "depth",
+ G_TYPE_INT, media_type->BitsPerSample, "width", G_TYPE_INT,
+ media_type->BitsPerSample, "block_align", G_TYPE_INT, media_type->BlockAlign, NULL);
+ break;
+ case TSMF_SUB_TYPE_WMA1:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "audio/x-wma", "wmaversion", G_TYPE_INT, 1, "rate", G_TYPE_INT,
+ media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT,
+ media_type->Channels, "bitrate", G_TYPE_INT, media_type->BitRate, "depth",
+ G_TYPE_INT, media_type->BitsPerSample, "width", G_TYPE_INT,
+ media_type->BitsPerSample, "block_align", G_TYPE_INT, media_type->BlockAlign, NULL);
+ break;
+ case TSMF_SUB_TYPE_WMA2:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "audio/x-wma", "wmaversion", G_TYPE_INT, 2, "rate", G_TYPE_INT,
+ media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT,
+ media_type->Channels, "bitrate", G_TYPE_INT, media_type->BitRate, "depth",
+ G_TYPE_INT, media_type->BitsPerSample, "width", G_TYPE_INT,
+ media_type->BitsPerSample, "block_align", G_TYPE_INT, media_type->BlockAlign, NULL);
+ break;
+ case TSMF_SUB_TYPE_MP3:
+ mdecoder->gst_caps =
+ gst_caps_new_simple("audio/mpeg", "mpegversion", G_TYPE_INT, 1, "layer", G_TYPE_INT,
+ 3, "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator,
+ "channels", G_TYPE_INT, media_type->Channels, NULL);
+ break;
+ case TSMF_SUB_TYPE_WMV1:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-wmv", "bitrate", G_TYPE_UINT, media_type->BitRate, "width", G_TYPE_INT,
+ media_type->Width, "height", G_TYPE_INT, media_type->Height, "wmvversion",
+ G_TYPE_INT, 1,
+#if GST_VERSION_MAJOR > 0
+ "format", G_TYPE_STRING, "WMV1",
+#else
+ "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'M', 'V', '1'),
+#endif
+ "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
+ media_type->SamplesPerSecond.Denominator, NULL);
+ break;
+ case TSMF_SUB_TYPE_WMV2:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-wmv", "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT,
+ media_type->Height, "wmvversion", G_TYPE_INT, 2,
+#if GST_VERSION_MAJOR > 0
+ "format", G_TYPE_STRING, "WMV2",
+#else
+ "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'M', 'V', '2'),
+#endif
+ "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
+ media_type->SamplesPerSecond.Denominator, "pixel-aspect-ratio", GST_TYPE_FRACTION,
+ 1, 1, NULL);
+ break;
+ case TSMF_SUB_TYPE_WMV3:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-wmv", "bitrate", G_TYPE_UINT, media_type->BitRate, "width", G_TYPE_INT,
+ media_type->Width, "height", G_TYPE_INT, media_type->Height, "wmvversion",
+ G_TYPE_INT, 3,
+#if GST_VERSION_MAJOR > 0
+ "format", G_TYPE_STRING, "WMV3",
+#else
+ "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'M', 'V', '3'),
+#endif
+ "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
+ media_type->SamplesPerSecond.Denominator, "pixel-aspect-ratio", GST_TYPE_FRACTION,
+ 1, 1, NULL);
+ break;
+ case TSMF_SUB_TYPE_AVC1:
+ case TSMF_SUB_TYPE_H264:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-h264", "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT,
+ media_type->Height, "framerate", GST_TYPE_FRACTION,
+ media_type->SamplesPerSecond.Numerator, media_type->SamplesPerSecond.Denominator,
+ "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, "stream-format", G_TYPE_STRING,
+ "byte-stream", "alignment", G_TYPE_STRING, "nal", NULL);
+ break;
+ case TSMF_SUB_TYPE_AC3:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "audio/x-ac3", "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator,
+ "channels", G_TYPE_INT, media_type->Channels, NULL);
+ break;
+ case TSMF_SUB_TYPE_AAC:
+
+ /* For AAC the pFormat is a HEAACWAVEINFO struct, and the codec data
+ is at the end of it. See
+ http://msdn.microsoft.com/en-us/library/dd757806.aspx */
+ if (media_type->ExtraData)
+ {
+ if (media_type->ExtraDataSize < 12)
+ return FALSE;
+ media_type->ExtraData += 12;
+ media_type->ExtraDataSize -= 12;
+ }
+
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "audio/mpeg", "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator,
+ "channels", G_TYPE_INT, media_type->Channels, "mpegversion", G_TYPE_INT, 4,
+ "framed", G_TYPE_BOOLEAN, TRUE, "stream-format", G_TYPE_STRING, "raw", NULL);
+ break;
+ case TSMF_SUB_TYPE_MP1A:
+ mdecoder->gst_caps =
+ gst_caps_new_simple("audio/mpeg", "mpegversion", G_TYPE_INT, 1, "channels",
+ G_TYPE_INT, media_type->Channels, NULL);
+ break;
+ case TSMF_SUB_TYPE_MP1V:
+ mdecoder->gst_caps =
+ gst_caps_new_simple("video/mpeg", "mpegversion", G_TYPE_INT, 1, "width", G_TYPE_INT,
+ media_type->Width, "height", G_TYPE_INT, media_type->Height,
+ "systemstream", G_TYPE_BOOLEAN, FALSE, NULL);
+ break;
+ case TSMF_SUB_TYPE_YUY2:
+#if GST_VERSION_MAJOR > 0
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-raw", "format", G_TYPE_STRING, "YUY2", "width", G_TYPE_INT,
+ media_type->Width, "height", G_TYPE_INT, media_type->Height, NULL);
+#else
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-raw-yuv", "format", G_TYPE_STRING, "YUY2", "width", G_TYPE_INT,
+ media_type->Width, "height", G_TYPE_INT, media_type->Height, "framerate",
+ GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
+ media_type->SamplesPerSecond.Denominator, NULL);
+#endif
+ break;
+ case TSMF_SUB_TYPE_MP2V:
+ mdecoder->gst_caps = gst_caps_new_simple("video/mpeg", "mpegversion", G_TYPE_INT, 2,
+ "systemstream", G_TYPE_BOOLEAN, FALSE, NULL);
+ break;
+ case TSMF_SUB_TYPE_MP2A:
+ mdecoder->gst_caps =
+ gst_caps_new_simple("audio/mpeg", "mpegversion", G_TYPE_INT, 1, "rate", G_TYPE_INT,
+ media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT,
+ media_type->Channels, NULL);
+ break;
+ case TSMF_SUB_TYPE_FLAC:
+ mdecoder->gst_caps = gst_caps_new_simple("audio/x-flac", "", NULL);
+ break;
+ default:
+ WLog_ERR(TAG, "unknown format:(%d).", media_type->SubType);
+ return FALSE;
+ }
+
+ if (media_type->ExtraDataSize > 0)
+ {
+ GstBuffer* buffer;
+ DEBUG_TSMF("Extra data available (%" PRIu32 ")", media_type->ExtraDataSize);
+ buffer = tsmf_get_buffer_from_data(media_type->ExtraData, media_type->ExtraDataSize);
+
+ if (!buffer)
+ {
+ WLog_ERR(TAG, "could not allocate GstBuffer!");
+ return FALSE;
+ }
+
+ gst_caps_set_simple(mdecoder->gst_caps, "codec_data", GST_TYPE_BUFFER, buffer, NULL);
+ }
+
+ DEBUG_TSMF("%p format '%s'", (void*)mdecoder, gst_caps_to_string(mdecoder->gst_caps));
+ tsmf_platform_set_format(mdecoder);
+
+ /* Create the pipeline... */
+ if (!tsmf_gstreamer_pipeline_build(mdecoder))
+ return FALSE;
+
+ return TRUE;
+}
+
+void tsmf_gstreamer_clean_up(TSMFGstreamerDecoder* mdecoder)
+{
+ if (!mdecoder || !mdecoder->pipe)
+ return;
+
+ if (mdecoder->pipe && GST_OBJECT_REFCOUNT_VALUE(mdecoder->pipe) > 0)
+ {
+ tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_NULL);
+ gst_object_unref(mdecoder->pipe);
+ }
+
+ mdecoder->ready = FALSE;
+ mdecoder->paused = FALSE;
+
+ mdecoder->pipe = NULL;
+ mdecoder->src = NULL;
+ mdecoder->queue = NULL;
+}
+
+BOOL tsmf_gstreamer_pipeline_build(TSMFGstreamerDecoder* mdecoder)
+{
+#if GST_VERSION_MAJOR > 0
+ const char* video =
+ "appsrc name=videosource ! queue2 name=videoqueue ! decodebin name=videodecoder !";
+ const char* audio =
+ "appsrc name=audiosource ! queue2 name=audioqueue ! decodebin name=audiodecoder ! "
+ "audioconvert ! audiorate ! audioresample ! volume name=audiovolume !";
+#else
+ const char* video =
+ "appsrc name=videosource ! queue2 name=videoqueue ! decodebin2 name=videodecoder !";
+ const char* audio =
+ "appsrc name=audiosource ! queue2 name=audioqueue ! decodebin2 name=audiodecoder ! "
+ "audioconvert ! audiorate ! audioresample ! volume name=audiovolume !";
+#endif
+ char pipeline[1024];
+
+ if (!mdecoder)
+ return FALSE;
+
+ /* TODO: Construction of the pipeline from a string allows easy overwrite with arguments.
+ * The only fixed elements necessary are appsrc and the volume element for audio streams.
+ * The rest could easily be provided in gstreamer pipeline notation from command line. */
+ if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
+ sprintf_s(pipeline, sizeof(pipeline), "%s %s name=videosink", video,
+ tsmf_platform_get_video_sink());
+ else
+ sprintf_s(pipeline, sizeof(pipeline), "%s %s name=audiosink", audio,
+ tsmf_platform_get_audio_sink());
+
+ DEBUG_TSMF("pipeline=%s", pipeline);
+ mdecoder->pipe = gst_parse_launch(pipeline, NULL);
+
+ if (!mdecoder->pipe)
+ {
+ WLog_ERR(TAG, "Failed to create new pipe");
+ return FALSE;
+ }
+
+ if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
+ mdecoder->src = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "videosource");
+ else
+ mdecoder->src = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audiosource");
+
+ if (!mdecoder->src)
+ {
+ WLog_ERR(TAG, "Failed to get appsrc");
+ return FALSE;
+ }
+
+ if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
+ mdecoder->queue = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "videoqueue");
+ else
+ mdecoder->queue = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audioqueue");
+
+ if (!mdecoder->queue)
+ {
+ WLog_ERR(TAG, "Failed to get queue");
+ return FALSE;
+ }
+
+ if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
+ mdecoder->outsink = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "videosink");
+ else
+ mdecoder->outsink = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audiosink");
+
+ if (!mdecoder->outsink)
+ {
+ WLog_ERR(TAG, "Failed to get sink");
+ return FALSE;
+ }
+
+ g_signal_connect(mdecoder->outsink, "child-added", G_CALLBACK(cb_child_added), mdecoder);
+
+ if (mdecoder->media_type == TSMF_MAJOR_TYPE_AUDIO)
+ {
+ mdecoder->volume = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audiovolume");
+
+ if (!mdecoder->volume)
+ {
+ WLog_ERR(TAG, "Failed to get volume");
+ return FALSE;
+ }
+
+ tsmf_gstreamer_change_volume((ITSMFDecoder*)mdecoder, mdecoder->gstVolume * ((double)10000),
+ mdecoder->gstMuted);
+ }
+
+ tsmf_platform_register_handler(mdecoder);
+ /* AppSrc settings */
+ GstAppSrcCallbacks callbacks = {
+ tsmf_gstreamer_need_data, tsmf_gstreamer_enough_data, tsmf_gstreamer_seek_data, { NULL }
+ };
+ g_object_set(mdecoder->src, "format", GST_FORMAT_TIME, NULL);
+ g_object_set(mdecoder->src, "is-live", FALSE, NULL);
+ g_object_set(mdecoder->src, "block", FALSE, NULL);
+ g_object_set(mdecoder->src, "blocksize", 1024, NULL);
+ gst_app_src_set_caps((GstAppSrc*)mdecoder->src, mdecoder->gst_caps);
+ gst_app_src_set_callbacks((GstAppSrc*)mdecoder->src, &callbacks, mdecoder, NULL);
+ gst_app_src_set_stream_type((GstAppSrc*)mdecoder->src, GST_APP_STREAM_TYPE_SEEKABLE);
+ gst_app_src_set_latency((GstAppSrc*)mdecoder->src, 0, -1);
+ gst_app_src_set_max_bytes((GstAppSrc*)mdecoder->src, (guint64)0); // unlimited
+ g_object_set(G_OBJECT(mdecoder->queue), "use-buffering", FALSE, NULL);
+ g_object_set(G_OBJECT(mdecoder->queue), "use-rate-estimate", FALSE, NULL);
+ g_object_set(G_OBJECT(mdecoder->queue), "max-size-buffers", 0, NULL);
+ g_object_set(G_OBJECT(mdecoder->queue), "max-size-bytes", 0, NULL);
+ g_object_set(G_OBJECT(mdecoder->queue), "max-size-time", (guint64)0, NULL);
+
+ /* Only set these properties if not an autosink, otherwise we will set properties when real
+ * sinks are added */
+ if (!g_strcmp0(G_OBJECT_TYPE_NAME(mdecoder->outsink), "GstAutoVideoSink") &&
+ !g_strcmp0(G_OBJECT_TYPE_NAME(mdecoder->outsink), "GstAutoAudioSink"))
+ {
+ if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
+ {
+ gst_base_sink_set_max_lateness((GstBaseSink*)mdecoder->outsink,
+ 10000000); /* nanoseconds */
+ }
+ else
+ {
+ gst_base_sink_set_max_lateness((GstBaseSink*)mdecoder->outsink,
+ 10000000); /* nanoseconds */
+ g_object_set(G_OBJECT(mdecoder->outsink), "buffer-time", (gint64)20000,
+ NULL); /* microseconds */
+ g_object_set(G_OBJECT(mdecoder->outsink), "drift-tolerance", (gint64)20000,
+ NULL); /* microseconds */
+ g_object_set(G_OBJECT(mdecoder->outsink), "latency-time", (gint64)10000,
+ NULL); /* microseconds */
+ g_object_set(G_OBJECT(mdecoder->outsink), "slave-method", 1, NULL);
+ }
+ g_object_set(G_OBJECT(mdecoder->outsink), "sync", TRUE,
+ NULL); /* synchronize on the clock */
+ g_object_set(G_OBJECT(mdecoder->outsink), "async", TRUE, NULL); /* no async state changes */
+ }
+
+ tsmf_window_create(mdecoder);
+ tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_READY);
+ tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PLAYING);
+ mdecoder->pipeline_start_time_valid = 0;
+ mdecoder->shutdown = 0;
+ mdecoder->paused = FALSE;
+
+ GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(mdecoder->pipe), GST_DEBUG_GRAPH_SHOW_ALL,
+ get_type(mdecoder));
+
+ return TRUE;
+}
+
+static BOOL tsmf_gstreamer_decodeEx(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size,
+ UINT32 extensions, UINT64 start_time, UINT64 end_time,
+ UINT64 duration)
+{
+ GstBuffer* gst_buf;
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+ UINT64 sample_time = tsmf_gstreamer_timestamp_ms_to_gst(start_time);
+ BOOL useTimestamps = TRUE;
+
+ if (!mdecoder)
+ {
+ WLog_ERR(TAG, "Decoder not initialized!");
+ return FALSE;
+ }
+
+ /*
+ * This function is always called from a stream-specific thread.
+ * It should be alright to block here if necessary.
+ * We don't expect to block here often, since the pipeline should
+ * have more than enough buffering.
+ */
+ DEBUG_TSMF(
+ "%s. Start:(%" PRIu64 ") End:(%" PRIu64 ") Duration:(%" PRIu64 ") Last Start:(%" PRIu64 ")",
+ get_type(mdecoder), start_time, end_time, duration, mdecoder->last_sample_start_time);
+
+ if (mdecoder->shutdown)
+ {
+ WLog_ERR(TAG, "decodeEx called on shutdown decoder");
+ return TRUE;
+ }
+
+ if (mdecoder->gst_caps == NULL)
+ {
+ WLog_ERR(TAG, "tsmf_gstreamer_set_format not called or invalid format.");
+ return FALSE;
+ }
+
+ if (!mdecoder->pipe)
+ tsmf_gstreamer_pipeline_build(mdecoder);
+
+ if (!mdecoder->src)
+ {
+ WLog_ERR(
+ TAG,
+ "failed to construct pipeline correctly. Unable to push buffer to source element.");
+ return FALSE;
+ }
+
+ gst_buf = tsmf_get_buffer_from_data(data, data_size);
+
+ if (gst_buf == NULL)
+ {
+ WLog_ERR(TAG, "tsmf_get_buffer_from_data(%p, %" PRIu32 ") failed.", (void*)data, data_size);
+ return FALSE;
+ }
+
+ /* Relative timestamping will sometimes be set to 0
+ * so we ignore these timestamps just to be safe(bit 8)
+ */
+ if (extensions & 0x00000080)
+ {
+ DEBUG_TSMF("Ignoring the timestamps - relative - bit 8");
+ useTimestamps = FALSE;
+ }
+
+ /* If no timestamps exist then we dont want to look at the timestamp values (bit 7) */
+ if (extensions & 0x00000040)
+ {
+ DEBUG_TSMF("Ignoring the timestamps - none - bit 7");
+ useTimestamps = FALSE;
+ }
+
+ /* If performing a seek */
+ if (mdecoder->seeking)
+ {
+ mdecoder->seeking = FALSE;
+ tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PAUSED);
+ mdecoder->pipeline_start_time_valid = 0;
+ }
+
+ if (mdecoder->pipeline_start_time_valid)
+ {
+ DEBUG_TSMF("%s start time %" PRIu64 "", get_type(mdecoder), start_time);
+
+ /* Adjusted the condition for a seek to be based on start time only
+ * WMV1 and WMV2 files in particular have bad end time and duration values
+ * there seems to be no real side effects of just using the start time instead
+ */
+ UINT64 minTime = mdecoder->last_sample_start_time - (UINT64)SEEK_TOLERANCE;
+ UINT64 maxTime = mdecoder->last_sample_start_time + (UINT64)SEEK_TOLERANCE;
+
+ /* Make sure the minTime stops at 0 , should we be at the beginning of the stream */
+ if (mdecoder->last_sample_start_time < (UINT64)SEEK_TOLERANCE)
+ minTime = 0;
+
+ /* If the start_time is valid and different from the previous start time by more than the
+ * seek tolerance, then we have a seek condition */
+ if (((start_time > maxTime) || (start_time < minTime)) && useTimestamps)
+ {
+ DEBUG_TSMF("tsmf_gstreamer_decodeEx: start_time=[%" PRIu64
+ "] > last_sample_start_time=[%" PRIu64 "] OR ",
+ start_time, mdecoder->last_sample_start_time);
+ DEBUG_TSMF("tsmf_gstreamer_decodeEx: start_time=[%" PRIu64
+ "] < last_sample_start_time=[%" PRIu64 "] with",
+ start_time, mdecoder->last_sample_start_time);
+ DEBUG_TSMF(
+ "tsmf_gstreamer_decodeEX: a tolerance of more than [%lu] from the last sample",
+ SEEK_TOLERANCE);
+ DEBUG_TSMF("tsmf_gstreamer_decodeEX: minTime=[%" PRIu64 "] maxTime=[%" PRIu64 "]",
+ minTime, maxTime);
+
+ mdecoder->seeking = TRUE;
+
+ /* since we cant make the gstreamer pipeline jump to the new start time after a seek -
+ * we just maintain a offset between realtime and gstreamer time
+ */
+ mdecoder->seek_offset = start_time;
+ }
+ }
+ else
+ {
+ DEBUG_TSMF("%s start time %" PRIu64 "", get_type(mdecoder), start_time);
+ /* Always set base/start time to 0. Will use seek offset to translate real buffer times
+ * back to 0. This allows the video to be started from anywhere and the ability to handle
+ * seeks without rebuilding the pipeline, etc. since that is costly
+ */
+ gst_element_set_base_time(mdecoder->pipe, tsmf_gstreamer_timestamp_ms_to_gst(0));
+ gst_element_set_start_time(mdecoder->pipe, tsmf_gstreamer_timestamp_ms_to_gst(0));
+ mdecoder->pipeline_start_time_valid = 1;
+
+ /* Set the seek offset if buffer has valid timestamps. */
+ if (useTimestamps)
+ mdecoder->seek_offset = start_time;
+
+ if (!gst_element_seek(mdecoder->pipe, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
+ GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE))
+ {
+ WLog_ERR(TAG, "seek failed");
+ }
+ }
+
+#if GST_VERSION_MAJOR > 0
+ if (useTimestamps)
+ GST_BUFFER_PTS(gst_buf) =
+ sample_time - tsmf_gstreamer_timestamp_ms_to_gst(mdecoder->seek_offset);
+ else
+ GST_BUFFER_PTS(gst_buf) = GST_CLOCK_TIME_NONE;
+#else
+ if (useTimestamps)
+ GST_BUFFER_TIMESTAMP(gst_buf) =
+ sample_time - tsmf_gstreamer_timestamp_ms_to_gst(mdecoder->seek_offset);
+ else
+ GST_BUFFER_TIMESTAMP(gst_buf) = GST_CLOCK_TIME_NONE;
+#endif
+ GST_BUFFER_DURATION(gst_buf) = GST_CLOCK_TIME_NONE;
+ GST_BUFFER_OFFSET(gst_buf) = GST_BUFFER_OFFSET_NONE;
+#if GST_VERSION_MAJOR > 0
+#else
+ gst_buffer_set_caps(gst_buf, mdecoder->gst_caps);
+#endif
+ gst_app_src_push_buffer(GST_APP_SRC(mdecoder->src), gst_buf);
+
+ /* Should only update the last timestamps if the current ones are valid */
+ if (useTimestamps)
+ {
+ mdecoder->last_sample_start_time = start_time;
+ mdecoder->last_sample_end_time = end_time;
+ }
+
+ if (mdecoder->pipe && (GST_STATE(mdecoder->pipe) != GST_STATE_PLAYING))
+ {
+ DEBUG_TSMF("%s: state=%s", get_type(mdecoder),
+ gst_element_state_get_name(GST_STATE(mdecoder->pipe)));
+
+ DEBUG_TSMF("%s Paused: %" PRIi32 " Shutdown: %i Ready: %" PRIi32 "", get_type(mdecoder),
+ mdecoder->paused, mdecoder->shutdown, mdecoder->ready);
+ if (!mdecoder->paused && !mdecoder->shutdown && mdecoder->ready)
+ tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PLAYING);
+ }
+
+ return TRUE;
+}
+
+static BOOL tsmf_gstreamer_control(ITSMFDecoder* decoder, ITSMFControlMsg control_msg, UINT32* arg)
+{
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+
+ if (!mdecoder)
+ {
+ WLog_ERR(TAG, "Control called with no decoder!");
+ return TRUE;
+ }
+
+ if (control_msg == Control_Pause)
+ {
+ DEBUG_TSMF("Control_Pause %s", get_type(mdecoder));
+
+ if (mdecoder->paused)
+ {
+ WLog_ERR(TAG, "%s: Ignoring Control_Pause, already received!", get_type(mdecoder));
+ return TRUE;
+ }
+
+ tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PAUSED);
+ mdecoder->shutdown = 0;
+ mdecoder->paused = TRUE;
+ }
+ else if (control_msg == Control_Resume)
+ {
+ DEBUG_TSMF("Control_Resume %s", get_type(mdecoder));
+
+ if (!mdecoder->paused && !mdecoder->shutdown)
+ {
+ WLog_ERR(TAG, "%s: Ignoring Control_Resume, already received!", get_type(mdecoder));
+ return TRUE;
+ }
+
+ mdecoder->shutdown = 0;
+ mdecoder->paused = FALSE;
+ }
+ else if (control_msg == Control_Stop)
+ {
+ DEBUG_TSMF("Control_Stop %s", get_type(mdecoder));
+
+ if (mdecoder->shutdown)
+ {
+ WLog_ERR(TAG, "%s: Ignoring Control_Stop, already received!", get_type(mdecoder));
+ return TRUE;
+ }
+
+ /* Reset stamps, flush buffers, etc */
+ if (mdecoder->pipe)
+ {
+ tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_NULL);
+ tsmf_window_destroy(mdecoder);
+ tsmf_gstreamer_clean_up(mdecoder);
+ }
+ mdecoder->seek_offset = 0;
+ mdecoder->pipeline_start_time_valid = 0;
+ mdecoder->shutdown = 1;
+ }
+ else if (control_msg == Control_Restart)
+ {
+ DEBUG_TSMF("Control_Restart %s", get_type(mdecoder));
+ mdecoder->shutdown = 0;
+ mdecoder->paused = FALSE;
+
+ if (mdecoder->pipeline_start_time_valid)
+ tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PLAYING);
+ }
+ else
+ WLog_ERR(TAG, "Unknown control message %08x", control_msg);
+
+ return TRUE;
+}
+
+static BOOL tsmf_gstreamer_buffer_level(ITSMFDecoder* decoder)
+{
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+ DEBUG_TSMF("");
+
+ if (!mdecoder)
+ return FALSE;
+
+ guint clbuff = 0;
+
+ if (G_IS_OBJECT(mdecoder->queue))
+ g_object_get(mdecoder->queue, "current-level-buffers", &clbuff, NULL);
+
+ DEBUG_TSMF("%s buffer level %u", get_type(mdecoder), clbuff);
+ return clbuff;
+}
+
+static void tsmf_gstreamer_free(ITSMFDecoder* decoder)
+{
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+ DEBUG_TSMF("%s", get_type(mdecoder));
+
+ if (mdecoder)
+ {
+ tsmf_window_destroy(mdecoder);
+ tsmf_gstreamer_clean_up(mdecoder);
+
+ if (mdecoder->gst_caps)
+ gst_caps_unref(mdecoder->gst_caps);
+
+ tsmf_platform_free(mdecoder);
+ ZeroMemory(mdecoder, sizeof(TSMFGstreamerDecoder));
+ free(mdecoder);
+ mdecoder = NULL;
+ }
+}
+
+static UINT64 tsmf_gstreamer_get_running_time(ITSMFDecoder* decoder)
+{
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+
+ if (!mdecoder)
+ return 0;
+
+ if (!mdecoder->outsink)
+ return mdecoder->last_sample_start_time;
+
+ if (!mdecoder->pipe)
+ return 0;
+
+ GstFormat fmt = GST_FORMAT_TIME;
+ gint64 pos = 0;
+#if GST_VERSION_MAJOR > 0
+ gst_element_query_position(mdecoder->pipe, fmt, &pos);
+#else
+ gst_element_query_position(mdecoder->pipe, &fmt, &pos);
+#endif
+ return (UINT64)(pos / 100 + mdecoder->seek_offset);
+}
+
+static BOOL tsmf_gstreamer_update_rendering_area(ITSMFDecoder* decoder, int newX, int newY,
+ int newWidth, int newHeight, int numRectangles,
+ RDP_RECT* rectangles)
+{
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+ DEBUG_TSMF("x=%d, y=%d, w=%d, h=%d, rect=%d", newX, newY, newWidth, newHeight, numRectangles);
+
+ if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
+ {
+ return tsmf_window_resize(mdecoder, newX, newY, newWidth, newHeight, numRectangles,
+ rectangles) == 0;
+ }
+
+ return TRUE;
+}
+
+static BOOL tsmf_gstreamer_ack(ITSMFDecoder* decoder, BOOL (*cb)(void*, BOOL), void* stream)
+{
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+ DEBUG_TSMF("");
+ mdecoder->ack_cb = NULL;
+ mdecoder->stream = stream;
+ return TRUE;
+}
+
+static BOOL tsmf_gstreamer_sync(ITSMFDecoder* decoder, void (*cb)(void*), void* stream)
+{
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+ DEBUG_TSMF("");
+ mdecoder->sync_cb = NULL;
+ mdecoder->stream = stream;
+ return TRUE;
+}
+
+FREERDP_ENTRY_POINT(ITSMFDecoder* gstreamer_freerdp_tsmf_client_decoder_subsystem_entry(void))
+{
+ TSMFGstreamerDecoder* decoder;
+
+#if GST_CHECK_VERSION(0, 10, 31)
+ if (!gst_is_initialized())
+ {
+ gst_init(NULL, NULL);
+ }
+#else
+ gst_init(NULL, NULL);
+#endif
+
+ decoder = calloc(1, sizeof(TSMFGstreamerDecoder));
+
+ if (!decoder)
+ return NULL;
+
+ decoder->iface.SetFormat = tsmf_gstreamer_set_format;
+ decoder->iface.Decode = NULL;
+ decoder->iface.GetDecodedData = NULL;
+ decoder->iface.GetDecodedFormat = NULL;
+ decoder->iface.GetDecodedDimension = NULL;
+ decoder->iface.GetRunningTime = tsmf_gstreamer_get_running_time;
+ decoder->iface.UpdateRenderingArea = tsmf_gstreamer_update_rendering_area;
+ decoder->iface.Free = tsmf_gstreamer_free;
+ decoder->iface.Control = tsmf_gstreamer_control;
+ decoder->iface.DecodeEx = tsmf_gstreamer_decodeEx;
+ decoder->iface.ChangeVolume = tsmf_gstreamer_change_volume;
+ decoder->iface.BufferLevel = tsmf_gstreamer_buffer_level;
+ decoder->iface.SetAckFunc = tsmf_gstreamer_ack;
+ decoder->iface.SetSyncFunc = tsmf_gstreamer_sync;
+ decoder->paused = FALSE;
+ decoder->gstVolume = 0.5;
+ decoder->gstMuted = FALSE;
+ decoder->state = GST_STATE_VOID_PENDING; /* No real state yet */
+ decoder->last_sample_start_time = 0;
+ decoder->last_sample_end_time = 0;
+ decoder->seek_offset = 0;
+ decoder->seeking = FALSE;
+
+ if (tsmf_platform_create(decoder) < 0)
+ {
+ free(decoder);
+ return NULL;
+ }
+
+ return (ITSMFDecoder*)decoder;
+}
diff --git a/channels/tsmf/client/gstreamer/tsmf_platform.h b/channels/tsmf/client/gstreamer/tsmf_platform.h
new file mode 100644
index 0000000..681095c
--- /dev/null
+++ b/channels/tsmf/client/gstreamer/tsmf_platform.h
@@ -0,0 +1,85 @@
+/*
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - GStreamer Decoder
+ * platform specific functions
+ *
+ * (C) Copyright 2014 Thincast Technologies GmbH
+ * (C) Copyright 2014 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_TSMF_CLIENT_GST_PLATFORM_H
+#define FREERDP_CHANNEL_TSMF_CLIENT_GST_PLATFORM_H
+
+#include <gst/gst.h>
+#include <tsmf_decoder.h>
+
+typedef struct
+{
+ ITSMFDecoder iface;
+
+ int media_type; /* TSMF_MAJOR_TYPE_AUDIO or TSMF_MAJOR_TYPE_VIDEO */
+
+ gint64 duration;
+
+ GstState state;
+ GstCaps* gst_caps;
+
+ GstElement* pipe;
+ GstElement* src;
+ GstElement* queue;
+ GstElement* outsink;
+ GstElement* volume;
+
+ BOOL ready;
+ BOOL paused;
+ UINT64 last_sample_start_time;
+ UINT64 last_sample_end_time;
+ BOOL seeking;
+ UINT64 seek_offset;
+
+ double gstVolume;
+ BOOL gstMuted;
+
+ int pipeline_start_time_valid; /* We've set the start time and have not reset the pipeline */
+ int shutdown; /* The decoder stream is shutting down */
+
+ void* platform;
+
+ BOOL (*ack_cb)(void*, BOOL);
+ void (*sync_cb)(void*);
+ void* stream;
+
+} TSMFGstreamerDecoder;
+
+const char* tsmf_platform_get_video_sink(void);
+const char* tsmf_platform_get_audio_sink(void);
+
+int tsmf_platform_create(TSMFGstreamerDecoder* decoder);
+int tsmf_platform_set_format(TSMFGstreamerDecoder* decoder);
+int tsmf_platform_register_handler(TSMFGstreamerDecoder* decoder);
+int tsmf_platform_free(TSMFGstreamerDecoder* decoder);
+
+int tsmf_window_create(TSMFGstreamerDecoder* decoder);
+int tsmf_window_resize(TSMFGstreamerDecoder* decoder, int x, int y, int width, int height,
+ int nr_rect, RDP_RECT* visible);
+int tsmf_window_destroy(TSMFGstreamerDecoder* decoder);
+
+int tsmf_window_map(TSMFGstreamerDecoder* decoder);
+int tsmf_window_unmap(TSMFGstreamerDecoder* decoder);
+
+BOOL tsmf_gstreamer_add_pad(TSMFGstreamerDecoder* mdecoder);
+void tsmf_gstreamer_remove_pad(TSMFGstreamerDecoder* mdecoder);
+
+#endif /* FREERDP_CHANNEL_TSMF_CLIENT_GST_PLATFORM_H */
diff --git a/channels/tsmf/client/oss/CMakeLists.txt b/channels/tsmf/client/oss/CMakeLists.txt
new file mode 100644
index 0000000..9ff9e81
--- /dev/null
+++ b/channels/tsmf/client/oss/CMakeLists.txt
@@ -0,0 +1,35 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("tsmf" "oss" "audio")
+
+find_package(OSS REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ tsmf_oss.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ ${OSS_LIBRARIES}
+)
+
+include_directories(..)
+include_directories(${OSS_INCLUDE_DIRS})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
+
diff --git a/channels/tsmf/client/oss/tsmf_oss.c b/channels/tsmf/client/oss/tsmf_oss.c
new file mode 100644
index 0000000..666b419
--- /dev/null
+++ b/channels/tsmf/client/oss/tsmf_oss.c
@@ -0,0 +1,248 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - OSS Audio Device
+ *
+ * Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <winpr/crt.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <unistd.h>
+#if defined(__OpenBSD__)
+#include <soundcard.h>
+#else
+#include <sys/soundcard.h>
+#endif
+#include <sys/ioctl.h>
+
+#include <freerdp/types.h>
+#include <freerdp/codec/dsp.h>
+
+#include "tsmf_audio.h"
+
+typedef struct
+{
+ ITSMFAudioDevice iface;
+
+ char dev_name[PATH_MAX];
+ int pcm_handle;
+
+ UINT32 sample_rate;
+ UINT32 channels;
+ UINT32 bits_per_sample;
+
+ UINT32 data_size_last;
+} TSMFOssAudioDevice;
+
+#define OSS_LOG_ERR(_text, _error) \
+ do \
+ { \
+ if ((_error) != 0) \
+ { \
+ char ebuffer[256] = { 0 }; \
+ WLog_ERR(TAG, "%s: %i - %s", (_text), (_error), \
+ winpr_strerror((_error), ebuffer, sizeof(ebuffer))); \
+ } \
+ } while (0)
+
+static BOOL tsmf_oss_open(ITSMFAudioDevice* audio, const char* device)
+{
+ int tmp = 0;
+ TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio;
+
+ if (oss == NULL || oss->pcm_handle != -1)
+ return FALSE;
+
+ if (device == NULL) /* Default device. */
+ {
+ strncpy(oss->dev_name, "/dev/dsp", sizeof(oss->dev_name));
+ }
+ else
+ {
+ strncpy(oss->dev_name, device, sizeof(oss->dev_name) - 1);
+ }
+
+ if ((oss->pcm_handle = open(oss->dev_name, O_WRONLY)) < 0)
+ {
+ OSS_LOG_ERR("sound dev open failed", errno);
+ oss->pcm_handle = -1;
+ return FALSE;
+ }
+
+#if 0 /* FreeBSD OSS implementation at this moment (2015.03) does not set PCM_CAP_OUTPUT flag. */
+ if (ioctl(oss->pcm_handle, SNDCTL_DSP_GETCAPS, &mask) == -1)
+ {
+ OSS_LOG_ERR("SNDCTL_DSP_GETCAPS failed, try ignory", errno);
+ }
+ else if ((mask & PCM_CAP_OUTPUT) == 0)
+ {
+ OSS_LOG_ERR("Device does not supports playback", EOPNOTSUPP);
+ close(oss->pcm_handle);
+ oss->pcm_handle = -1;
+ return FALSE;
+ }
+
+#endif
+ const int rc = ioctl(oss->pcm_handle, SNDCTL_DSP_GETFMTS, &tmp);
+ if (rc == -1)
+ {
+ OSS_LOG_ERR("SNDCTL_DSP_GETFMTS failed", errno);
+ close(oss->pcm_handle);
+ oss->pcm_handle = -1;
+ return FALSE;
+ }
+
+ if ((AFMT_S16_LE & tmp) == 0)
+ {
+ OSS_LOG_ERR("SNDCTL_DSP_GETFMTS - AFMT_S16_LE", EOPNOTSUPP);
+ close(oss->pcm_handle);
+ oss->pcm_handle = -1;
+ return FALSE;
+ }
+
+ WLog_INFO(TAG, "open: %s", oss->dev_name);
+ return TRUE;
+}
+
+static BOOL tsmf_oss_set_format(ITSMFAudioDevice* audio, UINT32 sample_rate, UINT32 channels,
+ UINT32 bits_per_sample)
+{
+ int tmp = 0;
+ TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio;
+
+ if (oss == NULL || oss->pcm_handle == -1)
+ return FALSE;
+
+ oss->sample_rate = sample_rate;
+ oss->channels = channels;
+ oss->bits_per_sample = bits_per_sample;
+ tmp = AFMT_S16_LE;
+
+ if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1)
+ OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed", errno);
+
+ tmp = channels;
+
+ if (ioctl(oss->pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1)
+ OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno);
+
+ tmp = sample_rate;
+
+ if (ioctl(oss->pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1)
+ OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno);
+
+ tmp = ((bits_per_sample / 8) * channels * sample_rate);
+
+ if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
+ OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed", errno);
+
+ DEBUG_TSMF("sample_rate %" PRIu32 " channels %" PRIu32 " bits_per_sample %" PRIu32 "",
+ sample_rate, channels, bits_per_sample);
+ return TRUE;
+}
+
+static BOOL tsmf_oss_play(ITSMFAudioDevice* audio, const BYTE* data, UINT32 data_size)
+{
+ int status = 0;
+ UINT32 offset = 0;
+ TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio;
+ DEBUG_TSMF("tsmf_oss_play: data_size %" PRIu32 "", data_size);
+
+ if (oss == NULL || oss->pcm_handle == -1)
+ return FALSE;
+
+ if (data == NULL || data_size == 0)
+ return TRUE;
+
+ offset = 0;
+ oss->data_size_last = data_size;
+
+ while (offset < data_size)
+ {
+ status = write(oss->pcm_handle, &data[offset], (data_size - offset));
+
+ if (status < 0)
+ {
+ OSS_LOG_ERR("write fail", errno);
+ return FALSE;
+ }
+
+ offset += status;
+ }
+
+ return TRUE;
+}
+
+static UINT64 tsmf_oss_get_latency(ITSMFAudioDevice* audio)
+{
+ UINT64 latency = 0;
+ TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio;
+
+ if (oss == NULL)
+ return 0;
+
+ // latency = ((oss->data_size_last / (oss->bits_per_sample / 8)) * oss->sample_rate);
+ // WLog_INFO(TAG, "latency: %zu", latency);
+ return latency;
+}
+
+static BOOL tsmf_oss_flush(ITSMFAudioDevice* audio)
+{
+ return TRUE;
+}
+
+static void tsmf_oss_free(ITSMFAudioDevice* audio)
+{
+ TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio;
+
+ if (oss == NULL)
+ return;
+
+ if (oss->pcm_handle != -1)
+ {
+ WLog_INFO(TAG, "close: %s", oss->dev_name);
+ close(oss->pcm_handle);
+ }
+
+ free(oss);
+}
+
+FREERDP_ENTRY_POINT(ITSMFAudioDevice* oss_freerdp_tsmf_client_audio_subsystem_entry(void))
+{
+ TSMFOssAudioDevice* oss = calloc(1, sizeof(TSMFOssAudioDevice));
+ if (!oss)
+ return NULL;
+
+ oss->iface.Open = tsmf_oss_open;
+ oss->iface.SetFormat = tsmf_oss_set_format;
+ oss->iface.Play = tsmf_oss_play;
+ oss->iface.GetLatency = tsmf_oss_get_latency;
+ oss->iface.Flush = tsmf_oss_flush;
+ oss->iface.Free = tsmf_oss_free;
+ oss->pcm_handle = -1;
+ return &oss->iface;
+}
diff --git a/channels/tsmf/client/pulse/CMakeLists.txt b/channels/tsmf/client/pulse/CMakeLists.txt
new file mode 100644
index 0000000..1aa67f0
--- /dev/null
+++ b/channels/tsmf/client/pulse/CMakeLists.txt
@@ -0,0 +1,35 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("tsmf" "pulse" "audio")
+
+find_package(PulseAudio REQUIRED)
+
+set(${MODULE_PREFIX}_SRCS
+ tsmf_pulse.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ ${PULSEAUDIO_LIBRARY}
+ ${PULSEAUDIO_MAINLOOP_LIBRARY}
+)
+
+include_directories(..)
+include_directories(${PULSEAUDIO_INCLUDE_DIR})
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/tsmf/client/pulse/tsmf_pulse.c b/channels/tsmf/client/pulse/tsmf_pulse.c
new file mode 100644
index 0000000..6d3cb1c
--- /dev/null
+++ b/channels/tsmf/client/pulse/tsmf_pulse.c
@@ -0,0 +1,414 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - PulseAudio Device
+ *
+ * Copyright 2010-2011 Vic Lee
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <winpr/crt.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "tsmf_audio.h"
+
+typedef struct
+{
+ ITSMFAudioDevice iface;
+
+ char device[32];
+ pa_threaded_mainloop* mainloop;
+ pa_context* context;
+ pa_sample_spec sample_spec;
+ pa_stream* stream;
+} TSMFPulseAudioDevice;
+
+static void tsmf_pulse_context_state_callback(pa_context* context, void* userdata)
+{
+ TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)userdata;
+ pa_context_state_t state = pa_context_get_state(context);
+
+ switch (state)
+ {
+ case PA_CONTEXT_READY:
+ DEBUG_TSMF("PA_CONTEXT_READY");
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+ break;
+
+ case PA_CONTEXT_FAILED:
+ case PA_CONTEXT_TERMINATED:
+ DEBUG_TSMF("state %d", state);
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+ break;
+
+ default:
+ DEBUG_TSMF("state %d", state);
+ break;
+ }
+}
+
+static BOOL tsmf_pulse_connect(TSMFPulseAudioDevice* pulse)
+{
+ pa_context_state_t state = PA_CONTEXT_FAILED;
+
+ if (!pulse->context)
+ return FALSE;
+
+ if (pa_context_connect(pulse->context, NULL, 0, NULL))
+ {
+ WLog_ERR(TAG, "pa_context_connect failed (%d)", pa_context_errno(pulse->context));
+ return FALSE;
+ }
+
+ pa_threaded_mainloop_lock(pulse->mainloop);
+
+ if (pa_threaded_mainloop_start(pulse->mainloop) < 0)
+ {
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ WLog_ERR(TAG, "pa_threaded_mainloop_start failed (%d)", pa_context_errno(pulse->context));
+ return FALSE;
+ }
+
+ for (;;)
+ {
+ state = pa_context_get_state(pulse->context);
+
+ if (state == PA_CONTEXT_READY)
+ break;
+
+ if (!PA_CONTEXT_IS_GOOD(state))
+ {
+ DEBUG_TSMF("bad context state (%d)", pa_context_errno(pulse->context));
+ break;
+ }
+
+ pa_threaded_mainloop_wait(pulse->mainloop);
+ }
+
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+
+ if (state == PA_CONTEXT_READY)
+ {
+ DEBUG_TSMF("connected");
+ return TRUE;
+ }
+ else
+ {
+ pa_context_disconnect(pulse->context);
+ return FALSE;
+ }
+}
+
+static BOOL tsmf_pulse_open(ITSMFAudioDevice* audio, const char* device)
+{
+ TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)audio;
+
+ if (device)
+ {
+ strncpy(pulse->device, device, sizeof(pulse->device) - 1);
+ }
+
+ pulse->mainloop = pa_threaded_mainloop_new();
+
+ if (!pulse->mainloop)
+ {
+ WLog_ERR(TAG, "pa_threaded_mainloop_new failed");
+ return FALSE;
+ }
+
+ pulse->context = pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), "freerdp");
+
+ if (!pulse->context)
+ {
+ WLog_ERR(TAG, "pa_context_new failed");
+ return FALSE;
+ }
+
+ pa_context_set_state_callback(pulse->context, tsmf_pulse_context_state_callback, pulse);
+
+ if (!tsmf_pulse_connect(pulse))
+ {
+ WLog_ERR(TAG, "tsmf_pulse_connect failed");
+ return FALSE;
+ }
+
+ DEBUG_TSMF("open device %s", pulse->device);
+ return TRUE;
+}
+
+static void tsmf_pulse_stream_success_callback(pa_stream* stream, int success, void* userdata)
+{
+ TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)userdata;
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+}
+
+static void tsmf_pulse_wait_for_operation(TSMFPulseAudioDevice* pulse, pa_operation* operation)
+{
+ if (operation == NULL)
+ return;
+
+ while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
+ {
+ pa_threaded_mainloop_wait(pulse->mainloop);
+ }
+
+ pa_operation_unref(operation);
+}
+
+static void tsmf_pulse_stream_state_callback(pa_stream* stream, void* userdata)
+{
+ TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)userdata;
+ pa_stream_state_t state = pa_stream_get_state(stream);
+
+ switch (state)
+ {
+ case PA_STREAM_READY:
+ DEBUG_TSMF("PA_STREAM_READY");
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+ break;
+
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ DEBUG_TSMF("state %d", state);
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+ break;
+
+ default:
+ DEBUG_TSMF("state %d", state);
+ break;
+ }
+}
+
+static void tsmf_pulse_stream_request_callback(pa_stream* stream, size_t length, void* userdata)
+{
+ TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)userdata;
+ DEBUG_TSMF("%" PRIdz "", length);
+ pa_threaded_mainloop_signal(pulse->mainloop, 0);
+}
+
+static BOOL tsmf_pulse_close_stream(TSMFPulseAudioDevice* pulse)
+{
+ if (!pulse->context || !pulse->stream)
+ return FALSE;
+
+ DEBUG_TSMF("");
+ pa_threaded_mainloop_lock(pulse->mainloop);
+ pa_stream_set_write_callback(pulse->stream, NULL, NULL);
+ tsmf_pulse_wait_for_operation(
+ pulse, pa_stream_drain(pulse->stream, tsmf_pulse_stream_success_callback, pulse));
+ pa_stream_disconnect(pulse->stream);
+ pa_stream_unref(pulse->stream);
+ pulse->stream = NULL;
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ return TRUE;
+}
+
+static BOOL tsmf_pulse_open_stream(TSMFPulseAudioDevice* pulse)
+{
+ pa_stream_state_t state = PA_STREAM_FAILED;
+ pa_buffer_attr buffer_attr = { 0 };
+
+ if (!pulse->context)
+ return FALSE;
+
+ DEBUG_TSMF("");
+ pa_threaded_mainloop_lock(pulse->mainloop);
+ pulse->stream = pa_stream_new(pulse->context, "freerdp", &pulse->sample_spec, NULL);
+
+ if (!pulse->stream)
+ {
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ WLog_ERR(TAG, "pa_stream_new failed (%d)", pa_context_errno(pulse->context));
+ return FALSE;
+ }
+
+ pa_stream_set_state_callback(pulse->stream, tsmf_pulse_stream_state_callback, pulse);
+ pa_stream_set_write_callback(pulse->stream, tsmf_pulse_stream_request_callback, pulse);
+ buffer_attr.maxlength = pa_usec_to_bytes(500000, &pulse->sample_spec);
+ buffer_attr.tlength = pa_usec_to_bytes(250000, &pulse->sample_spec);
+ buffer_attr.prebuf = (UINT32)-1;
+ buffer_attr.minreq = (UINT32)-1;
+ buffer_attr.fragsize = (UINT32)-1;
+
+ if (pa_stream_connect_playback(
+ pulse->stream, pulse->device[0] ? pulse->device : NULL, &buffer_attr,
+ PA_STREAM_ADJUST_LATENCY | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE,
+ NULL, NULL) < 0)
+ {
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ WLog_ERR(TAG, "pa_stream_connect_playback failed (%d)", pa_context_errno(pulse->context));
+ return FALSE;
+ }
+
+ for (;;)
+ {
+ state = pa_stream_get_state(pulse->stream);
+
+ if (state == PA_STREAM_READY)
+ break;
+
+ if (!PA_STREAM_IS_GOOD(state))
+ {
+ WLog_ERR(TAG, "bad stream state (%d)", pa_context_errno(pulse->context));
+ break;
+ }
+
+ pa_threaded_mainloop_wait(pulse->mainloop);
+ }
+
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+
+ if (state == PA_STREAM_READY)
+ {
+ DEBUG_TSMF("connected");
+ return TRUE;
+ }
+ else
+ {
+ tsmf_pulse_close_stream(pulse);
+ return FALSE;
+ }
+}
+
+static BOOL tsmf_pulse_set_format(ITSMFAudioDevice* audio, UINT32 sample_rate, UINT32 channels,
+ UINT32 bits_per_sample)
+{
+ TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)audio;
+ DEBUG_TSMF("sample_rate %" PRIu32 " channels %" PRIu32 " bits_per_sample %" PRIu32 "",
+ sample_rate, channels, bits_per_sample);
+ pulse->sample_spec.rate = sample_rate;
+ pulse->sample_spec.channels = channels;
+ pulse->sample_spec.format = PA_SAMPLE_S16LE;
+ return tsmf_pulse_open_stream(pulse);
+}
+
+static BOOL tsmf_pulse_play(ITSMFAudioDevice* audio, const BYTE* data, UINT32 data_size)
+{
+ TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)audio;
+ const BYTE* src = NULL;
+ size_t len = 0;
+ int ret = 0;
+ DEBUG_TSMF("data_size %" PRIu32 "", data_size);
+
+ if (pulse->stream)
+ {
+ pa_threaded_mainloop_lock(pulse->mainloop);
+ src = data;
+
+ while (data_size > 0)
+ {
+ while ((len = pa_stream_writable_size(pulse->stream)) == 0)
+ {
+ DEBUG_TSMF("waiting");
+ pa_threaded_mainloop_wait(pulse->mainloop);
+ }
+
+ if (len == (size_t)-1)
+ break;
+
+ if (len > data_size)
+ len = data_size;
+
+ ret = pa_stream_write(pulse->stream, src, len, NULL, 0LL, PA_SEEK_RELATIVE);
+
+ if (ret < 0)
+ {
+ DEBUG_TSMF("pa_stream_write failed (%d)", pa_context_errno(pulse->context));
+ break;
+ }
+
+ src += len;
+ data_size -= len;
+ }
+
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ }
+
+ return TRUE;
+}
+
+static UINT64 tsmf_pulse_get_latency(ITSMFAudioDevice* audio)
+{
+ pa_usec_t usec = 0;
+ UINT64 latency = 0;
+ TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)audio;
+
+ if (pulse->stream && pa_stream_get_latency(pulse->stream, &usec, NULL) == 0)
+ {
+ latency = ((UINT64)usec) * 10LL;
+ }
+
+ return latency;
+}
+
+static BOOL tsmf_pulse_flush(ITSMFAudioDevice* audio)
+{
+ TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)audio;
+ pa_threaded_mainloop_lock(pulse->mainloop);
+ tsmf_pulse_wait_for_operation(
+ pulse, pa_stream_flush(pulse->stream, tsmf_pulse_stream_success_callback, pulse));
+ pa_threaded_mainloop_unlock(pulse->mainloop);
+ return TRUE;
+}
+
+static void tsmf_pulse_free(ITSMFAudioDevice* audio)
+{
+ TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)audio;
+ DEBUG_TSMF("");
+ tsmf_pulse_close_stream(pulse);
+
+ if (pulse->mainloop)
+ {
+ pa_threaded_mainloop_stop(pulse->mainloop);
+ }
+
+ if (pulse->context)
+ {
+ pa_context_disconnect(pulse->context);
+ pa_context_unref(pulse->context);
+ pulse->context = NULL;
+ }
+
+ if (pulse->mainloop)
+ {
+ pa_threaded_mainloop_free(pulse->mainloop);
+ pulse->mainloop = NULL;
+ }
+
+ free(pulse);
+}
+
+FREERDP_ENTRY_POINT(ITSMFAudioDevice* pulse_freerdp_tsmf_client_audio_subsystem_entry(void))
+{
+ TSMFPulseAudioDevice* pulse = NULL;
+ pulse = (TSMFPulseAudioDevice*)calloc(1, sizeof(TSMFPulseAudioDevice));
+
+ if (!pulse)
+ return NULL;
+
+ pulse->iface.Open = tsmf_pulse_open;
+ pulse->iface.SetFormat = tsmf_pulse_set_format;
+ pulse->iface.Play = tsmf_pulse_play;
+ pulse->iface.GetLatency = tsmf_pulse_get_latency;
+ pulse->iface.Flush = tsmf_pulse_flush;
+ pulse->iface.Free = tsmf_pulse_free;
+ return (ITSMFAudioDevice*)pulse;
+}
diff --git a/channels/tsmf/client/tsmf_audio.c b/channels/tsmf/client/tsmf_audio.c
new file mode 100644
index 0000000..89202ef
--- /dev/null
+++ b/channels/tsmf/client/tsmf_audio.c
@@ -0,0 +1,97 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - Audio Device Manager
+ *
+ * Copyright 2010-2011 Vic Lee
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tsmf_audio.h"
+
+static ITSMFAudioDevice* tsmf_load_audio_device_by_name(const char* name, const char* device)
+{
+ ITSMFAudioDevice* audio = NULL;
+ TSMF_AUDIO_DEVICE_ENTRY entry = NULL;
+
+ entry =
+ (TSMF_AUDIO_DEVICE_ENTRY)(void*)freerdp_load_channel_addin_entry("tsmf", name, "audio", 0);
+
+ if (!entry)
+ return NULL;
+
+ audio = entry();
+
+ if (!audio)
+ {
+ WLog_ERR(TAG, "failed to call export function in %s", name);
+ return NULL;
+ }
+
+ if (!audio->Open(audio, device))
+ {
+ audio->Free(audio);
+ audio = NULL;
+ WLog_ERR(TAG, "failed to open, name: %s, device: %s", name, device);
+ }
+ else
+ {
+ WLog_DBG(TAG, "name: %s, device: %s", name, device);
+ }
+
+ return audio;
+}
+
+ITSMFAudioDevice* tsmf_load_audio_device(const char* name, const char* device)
+{
+ ITSMFAudioDevice* audio = NULL;
+
+ if (name)
+ {
+ audio = tsmf_load_audio_device_by_name(name, device);
+ }
+ else
+ {
+#if defined(WITH_PULSE)
+ if (!audio)
+ audio = tsmf_load_audio_device_by_name("pulse", device);
+#endif
+
+#if defined(WITH_OSS)
+ if (!audio)
+ audio = tsmf_load_audio_device_by_name("oss", device);
+#endif
+
+#if defined(WITH_ALSA)
+ if (!audio)
+ audio = tsmf_load_audio_device_by_name("alsa", device);
+#endif
+ }
+
+ if (audio == NULL)
+ {
+ WLog_ERR(TAG, "no sound device.");
+ }
+ else
+ {
+ WLog_DBG(TAG, "name: %s, device: %s", name, device);
+ }
+
+ return audio;
+}
diff --git a/channels/tsmf/client/tsmf_audio.h b/channels/tsmf/client/tsmf_audio.h
new file mode 100644
index 0000000..fc783d0
--- /dev/null
+++ b/channels/tsmf/client/tsmf_audio.h
@@ -0,0 +1,51 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - Audio Device Manager
+ *
+ * Copyright 2010-2011 Vic Lee
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_TSMF_CLIENT_AUDIO_H
+#define FREERDP_CHANNEL_TSMF_CLIENT_AUDIO_H
+
+#include "tsmf_types.h"
+
+typedef struct s_ITSMFAudioDevice ITSMFAudioDevice;
+
+struct s_ITSMFAudioDevice
+{
+ /* Open the audio device. */
+ BOOL (*Open)(ITSMFAudioDevice* audio, const char* device);
+ /* Set the audio data format. */
+ BOOL(*SetFormat)
+ (ITSMFAudioDevice* audio, UINT32 sample_rate, UINT32 channels, UINT32 bits_per_sample);
+ /* Play audio data. */
+ BOOL (*Play)(ITSMFAudioDevice* audio, const BYTE* data, UINT32 data_size);
+ /* Get the latency of the last written sample, in 100ns */
+ UINT64 (*GetLatency)(ITSMFAudioDevice* audio);
+ /* Change the playback volume level */
+ BOOL (*ChangeVolume)(ITSMFAudioDevice* audio, UINT32 newVolume, UINT32 muted);
+ /* Flush queued audio data */
+ BOOL (*Flush)(ITSMFAudioDevice* audio);
+ /* Free the audio device */
+ void (*Free)(ITSMFAudioDevice* audio);
+};
+
+#define TSMF_AUDIO_DEVICE_EXPORT_FUNC_NAME "TSMFAudioDeviceEntry"
+typedef ITSMFAudioDevice* (*TSMF_AUDIO_DEVICE_ENTRY)(void);
+
+ITSMFAudioDevice* tsmf_load_audio_device(const char* name, const char* device);
+
+#endif /* FREERDP_CHANNEL_TSMF_CLIENT_AUDIO_H */
diff --git a/channels/tsmf/client/tsmf_codec.c b/channels/tsmf/client/tsmf_codec.c
new file mode 100644
index 0000000..d3f6707
--- /dev/null
+++ b/channels/tsmf/client/tsmf_codec.c
@@ -0,0 +1,614 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - Codec
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2012 Hewlett-Packard Development Company, L.P.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+#include <winpr/print.h>
+
+#include "tsmf_decoder.h"
+#include "tsmf_constants.h"
+#include "tsmf_types.h"
+
+#include "tsmf_codec.h"
+
+#include <freerdp/log.h>
+
+#define TAG CHANNELS_TAG("tsmf.client")
+
+typedef struct
+{
+ BYTE guid[16];
+ const char* name;
+ int type;
+} TSMFMediaTypeMap;
+
+static const TSMFMediaTypeMap tsmf_major_type_map[] = {
+ /* 73646976-0000-0010-8000-00AA00389B71 */
+ { { 0x76, 0x69, 0x64, 0x73, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIATYPE_Video",
+ TSMF_MAJOR_TYPE_VIDEO },
+
+ /* 73647561-0000-0010-8000-00AA00389B71 */
+ { { 0x61, 0x75, 0x64, 0x73, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIATYPE_Audio",
+ TSMF_MAJOR_TYPE_AUDIO },
+
+ { { 0 }, "Unknown", TSMF_MAJOR_TYPE_UNKNOWN }
+};
+
+static const TSMFMediaTypeMap tsmf_sub_type_map[] = {
+ /* 31435657-0000-0010-8000-00AA00389B71 */
+ { { 0x57, 0x56, 0x43, 0x31, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_WVC1",
+ TSMF_SUB_TYPE_WVC1 },
+
+ /* 00000160-0000-0010-8000-00AA00389B71 */
+ { { 0x60, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_WMAudioV1", /* V7, V8 has the same GUID */
+ TSMF_SUB_TYPE_WMA1 },
+
+ /* 00000161-0000-0010-8000-00AA00389B71 */
+ { { 0x61, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_WMAudioV2", /* V7, V8 has the same GUID */
+ TSMF_SUB_TYPE_WMA2 },
+
+ /* 00000162-0000-0010-8000-00AA00389B71 */
+ { { 0x62, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_WMAudioV9",
+ TSMF_SUB_TYPE_WMA9 },
+
+ /* 00000055-0000-0010-8000-00AA00389B71 */
+ { { 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_MP3",
+ TSMF_SUB_TYPE_MP3 },
+
+ /* E06D802B-DB46-11CF-B4D1-00805F6CBBEA */
+ { { 0x2B, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB,
+ 0xEA },
+ "MEDIASUBTYPE_MPEG2_AUDIO",
+ TSMF_SUB_TYPE_MP2A },
+
+ /* E06D8026-DB46-11CF-B4D1-00805F6CBBEA */
+ { { 0x26, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB,
+ 0xEA },
+ "MEDIASUBTYPE_MPEG2_VIDEO",
+ TSMF_SUB_TYPE_MP2V },
+
+ /* 31564D57-0000-0010-8000-00AA00389B71 */
+ { { 0x57, 0x4D, 0x56, 0x31, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_WMV1",
+ TSMF_SUB_TYPE_WMV1 },
+
+ /* 32564D57-0000-0010-8000-00AA00389B71 */
+ { { 0x57, 0x4D, 0x56, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_WMV2",
+ TSMF_SUB_TYPE_WMV2 },
+
+ /* 33564D57-0000-0010-8000-00AA00389B71 */
+ { { 0x57, 0x4D, 0x56, 0x33, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_WMV3",
+ TSMF_SUB_TYPE_WMV3 },
+
+ /* 00001610-0000-0010-8000-00AA00389B71 */
+ { { 0x10, 0x16, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_MPEG_HEAAC",
+ TSMF_SUB_TYPE_AAC },
+
+ /* 34363248-0000-0010-8000-00AA00389B71 */
+ { { 0x48, 0x32, 0x36, 0x34, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_H264",
+ TSMF_SUB_TYPE_H264 },
+
+ /* 31435641-0000-0010-8000-00AA00389B71 */
+ { { 0x41, 0x56, 0x43, 0x31, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_AVC1",
+ TSMF_SUB_TYPE_AVC1 },
+
+ /* 3334504D-0000-0010-8000-00AA00389B71 */
+ { { 0x4D, 0x50, 0x34, 0x33, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_MP43",
+ TSMF_SUB_TYPE_MP43 },
+
+ /* 5634504D-0000-0010-8000-00AA00389B71 */
+ { { 0x4D, 0x50, 0x34, 0x56, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_MP4S",
+ TSMF_SUB_TYPE_MP4S },
+
+ /* 3234504D-0000-0010-8000-00AA00389B71 */
+ { { 0x4D, 0x50, 0x34, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_MP42",
+ TSMF_SUB_TYPE_MP42 },
+
+ /* 3253344D-0000-0010-8000-00AA00389B71 */
+ { { 0x4D, 0x34, 0x53, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_MP42",
+ TSMF_SUB_TYPE_M4S2 },
+
+ /* E436EB81-524F-11CE-9F53-0020AF0BA770 */
+ { { 0x81, 0xEB, 0x36, 0xE4, 0x4F, 0x52, 0xCE, 0x11, 0x9F, 0x53, 0x00, 0x20, 0xAF, 0x0B, 0xA7,
+ 0x70 },
+ "MEDIASUBTYPE_MP1V",
+ TSMF_SUB_TYPE_MP1V },
+
+ /* 00000050-0000-0010-8000-00AA00389B71 */
+ { { 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_MP1A",
+ TSMF_SUB_TYPE_MP1A },
+
+ /* E06D802C-DB46-11CF-B4D1-00805F6CBBEA */
+ { { 0x2C, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB,
+ 0xEA },
+ "MEDIASUBTYPE_DOLBY_AC3",
+ TSMF_SUB_TYPE_AC3 },
+
+ /* 32595559-0000-0010-8000-00AA00389B71 */
+ { { 0x59, 0x55, 0x59, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_YUY2",
+ TSMF_SUB_TYPE_YUY2 },
+
+ /* Opencodec IDS */
+ { { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_FLAC",
+ TSMF_SUB_TYPE_FLAC },
+
+ { { 0x61, 0x34, 0x70, 0x6D, 0x7A, 0x76, 0x4D, 0x49, 0xB4, 0x78, 0xF2, 0x9D, 0x25, 0xDC, 0x90,
+ 0x37 },
+ "MEDIASUBTYPE_OGG",
+ TSMF_SUB_TYPE_OGG },
+
+ { { 0x4D, 0x34, 0x53, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_H263",
+ TSMF_SUB_TYPE_H263 },
+
+ /* WebMMF codec IDS */
+ { { 0x56, 0x50, 0x38, 0x30, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71 },
+ "MEDIASUBTYPE_VP8",
+ TSMF_SUB_TYPE_VP8 },
+
+ { { 0x0B, 0xD1, 0x2F, 0x8D, 0x41, 0x58, 0x6B, 0x4A, 0x89, 0x05, 0x58, 0x8F, 0xEC, 0x1A, 0xDE,
+ 0xD9 },
+ "MEDIASUBTYPE_OGG",
+ TSMF_SUB_TYPE_OGG },
+
+ { { 0 }, "Unknown", TSMF_SUB_TYPE_UNKNOWN }
+
+};
+
+static const TSMFMediaTypeMap tsmf_format_type_map[] = {
+ /* AED4AB2D-7326-43CB-9464-C879CAB9C43D */
+ { { 0x2D, 0xAB, 0xD4, 0xAE, 0x26, 0x73, 0xCB, 0x43, 0x94, 0x64, 0xC8, 0x79, 0xCA, 0xB9, 0xC4,
+ 0x3D },
+ "FORMAT_MFVideoFormat",
+ TSMF_FORMAT_TYPE_MFVIDEOFORMAT },
+
+ /* 05589F81-C356-11CE-BF01-00AA0055595A */
+ { { 0x81, 0x9F, 0x58, 0x05, 0x56, 0xC3, 0xCE, 0x11, 0xBF, 0x01, 0x00, 0xAA, 0x00, 0x55, 0x59,
+ 0x5A },
+ "FORMAT_WaveFormatEx",
+ TSMF_FORMAT_TYPE_WAVEFORMATEX },
+
+ /* E06D80E3-DB46-11CF-B4D1-00805F6CBBEA */
+ { { 0xE3, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB,
+ 0xEA },
+ "FORMAT_MPEG2_VIDEO",
+ TSMF_FORMAT_TYPE_MPEG2VIDEOINFO },
+
+ /* F72A76A0-EB0A-11D0-ACE4-0000C0CC16BA */
+ { { 0xA0, 0x76, 0x2A, 0xF7, 0x0A, 0xEB, 0xD0, 0x11, 0xAC, 0xE4, 0x00, 0x00, 0xC0, 0xCC, 0x16,
+ 0xBA },
+ "FORMAT_VideoInfo2",
+ TSMF_FORMAT_TYPE_VIDEOINFO2 },
+
+ /* 05589F82-C356-11CE-BF01-00AA0055595A */
+ { { 0x82, 0x9F, 0x58, 0x05, 0x56, 0xC3, 0xCE, 0x11, 0xBF, 0x01, 0x00, 0xAA, 0x00, 0x55, 0x59,
+ 0x5A },
+ "FORMAT_MPEG1_VIDEO",
+ TSMF_FORMAT_TYPE_MPEG1VIDEOINFO },
+
+ { { 0 }, "Unknown", TSMF_FORMAT_TYPE_UNKNOWN }
+};
+
+static void tsmf_print_guid(const BYTE* guid)
+{
+#ifdef WITH_DEBUG_TSMF
+ char guidString[37];
+
+ snprintf(guidString, sizeof(guidString),
+ "%02" PRIX8 "%02" PRIX8 "%02" PRIX8 "%02" PRIX8 "-%02" PRIX8 "%02" PRIX8 "-%02" PRIX8
+ "%02" PRIX8 "-%02" PRIX8 "%02" PRIX8 "-%02" PRIX8 "%02" PRIX8 "%02" PRIX8 "%02" PRIX8
+ "%02" PRIX8 "%02" PRIX8 "",
+ guid[3], guid[2], guid[1], guid[0], guid[5], guid[4], guid[7], guid[6], guid[8],
+ guid[9], guid[10], guid[11], guid[12], guid[13], guid[14], guid[15]);
+
+ WLog_INFO(TAG, "%s", guidString);
+#endif
+}
+
+/* http://msdn.microsoft.com/en-us/library/dd318229.aspx */
+static UINT32 tsmf_codec_parse_BITMAPINFOHEADER(TS_AM_MEDIA_TYPE* mediatype, wStream* s,
+ BOOL bypass)
+{
+ UINT32 biSize = 0;
+ UINT32 biWidth = 0;
+ UINT32 biHeight = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 40))
+ return 0;
+ Stream_Read_UINT32(s, biSize);
+ Stream_Read_UINT32(s, biWidth);
+ Stream_Read_UINT32(s, biHeight);
+ Stream_Seek(s, 28);
+
+ if (mediatype->Width == 0)
+ mediatype->Width = biWidth;
+
+ if (mediatype->Height == 0)
+ mediatype->Height = biHeight;
+
+ /* Assume there will be no color table for video? */
+ if (biSize < 40)
+ return 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (biSize - 40)))
+ return 0;
+
+ if (bypass && biSize > 40)
+ Stream_Seek(s, biSize - 40);
+
+ return (bypass ? biSize : 40);
+}
+
+/* http://msdn.microsoft.com/en-us/library/dd407326.aspx */
+static UINT32 tsmf_codec_parse_VIDEOINFOHEADER2(TS_AM_MEDIA_TYPE* mediatype, wStream* s)
+{
+ UINT64 AvgTimePerFrame = 0;
+
+ /* VIDEOINFOHEADER2.rcSource, RECT(LONG left, LONG top, LONG right, LONG bottom) */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 72))
+ return 0;
+
+ Stream_Seek_UINT32(s);
+ Stream_Seek_UINT32(s);
+ Stream_Read_UINT32(s, mediatype->Width);
+ Stream_Read_UINT32(s, mediatype->Height);
+ /* VIDEOINFOHEADER2.rcTarget */
+ Stream_Seek(s, 16);
+ /* VIDEOINFOHEADER2.dwBitRate */
+ Stream_Read_UINT32(s, mediatype->BitRate);
+ /* VIDEOINFOHEADER2.dwBitErrorRate */
+ Stream_Seek_UINT32(s);
+ /* VIDEOINFOHEADER2.AvgTimePerFrame */
+ Stream_Read_UINT64(s, AvgTimePerFrame);
+ mediatype->SamplesPerSecond.Numerator = 1000000;
+ mediatype->SamplesPerSecond.Denominator = (int)(AvgTimePerFrame / 10LL);
+ /* Remaining fields before bmiHeader */
+ Stream_Seek(s, 24);
+ return 72;
+}
+
+/* http://msdn.microsoft.com/en-us/library/dd390700.aspx */
+static UINT32 tsmf_codec_parse_VIDEOINFOHEADER(TS_AM_MEDIA_TYPE* mediatype, wStream* s)
+{
+ /*
+ typedef struct {
+ RECT rcSource; //16
+ RECT rcTarget; //16 32
+ DWORD dwBitRate; //4 36
+ DWORD dwBitErrorRate; //4 40
+ REFERENCE_TIME AvgTimePerFrame; //8 48
+ BITMAPINFOHEADER bmiHeader;
+ } VIDEOINFOHEADER;
+ */
+ UINT64 AvgTimePerFrame = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 48))
+ return 0;
+
+ /* VIDEOINFOHEADER.rcSource, RECT(LONG left, LONG top, LONG right, LONG bottom) */
+ Stream_Seek_UINT32(s);
+ Stream_Seek_UINT32(s);
+ Stream_Read_UINT32(s, mediatype->Width);
+ Stream_Read_UINT32(s, mediatype->Height);
+ /* VIDEOINFOHEADER.rcTarget */
+ Stream_Seek(s, 16);
+ /* VIDEOINFOHEADER.dwBitRate */
+ Stream_Read_UINT32(s, mediatype->BitRate);
+ /* VIDEOINFOHEADER.dwBitErrorRate */
+ Stream_Seek_UINT32(s);
+ /* VIDEOINFOHEADER.AvgTimePerFrame */
+ Stream_Read_UINT64(s, AvgTimePerFrame);
+ mediatype->SamplesPerSecond.Numerator = 1000000;
+ mediatype->SamplesPerSecond.Denominator = (int)(AvgTimePerFrame / 10LL);
+ return 48;
+}
+
+static BOOL tsmf_read_format_type(TS_AM_MEDIA_TYPE* mediatype, wStream* s, UINT32 cbFormat)
+{
+ UINT32 i = 0;
+ UINT32 j = 0;
+
+ switch (mediatype->FormatType)
+ {
+ case TSMF_FORMAT_TYPE_MFVIDEOFORMAT:
+ /* http://msdn.microsoft.com/en-us/library/aa473808.aspx */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 176))
+ return FALSE;
+
+ Stream_Seek(s, 8); /* dwSize and ? */
+ Stream_Read_UINT32(s, mediatype->Width); /* videoInfo.dwWidth */
+ Stream_Read_UINT32(s, mediatype->Height); /* videoInfo.dwHeight */
+ Stream_Seek(s, 32);
+ /* videoInfo.FramesPerSecond */
+ Stream_Read_UINT32(s, mediatype->SamplesPerSecond.Numerator);
+ Stream_Read_UINT32(s, mediatype->SamplesPerSecond.Denominator);
+ Stream_Seek(s, 80);
+ Stream_Read_UINT32(s, mediatype->BitRate); /* compressedInfo.AvgBitrate */
+ Stream_Seek(s, 36);
+
+ if (cbFormat > 176)
+ {
+ const size_t nsize = cbFormat - 176;
+ if (mediatype->ExtraDataSize < nsize)
+ return FALSE;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, nsize))
+ return FALSE;
+ mediatype->ExtraDataSize = nsize;
+ mediatype->ExtraData = Stream_Pointer(s);
+ }
+ break;
+
+ case TSMF_FORMAT_TYPE_WAVEFORMATEX:
+ /* http://msdn.microsoft.com/en-us/library/dd757720.aspx */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 18))
+ return FALSE;
+
+ Stream_Seek_UINT16(s);
+ Stream_Read_UINT16(s, mediatype->Channels);
+ Stream_Read_UINT32(s, mediatype->SamplesPerSecond.Numerator);
+ mediatype->SamplesPerSecond.Denominator = 1;
+ Stream_Read_UINT32(s, mediatype->BitRate);
+ mediatype->BitRate *= 8;
+ Stream_Read_UINT16(s, mediatype->BlockAlign);
+ Stream_Read_UINT16(s, mediatype->BitsPerSample);
+ Stream_Read_UINT16(s, mediatype->ExtraDataSize);
+
+ if (mediatype->ExtraDataSize > 0)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, mediatype->ExtraDataSize))
+ return FALSE;
+ mediatype->ExtraData = Stream_Pointer(s);
+ }
+ break;
+
+ case TSMF_FORMAT_TYPE_MPEG1VIDEOINFO:
+ /* http://msdn.microsoft.com/en-us/library/dd390700.aspx */
+ i = tsmf_codec_parse_VIDEOINFOHEADER(mediatype, s);
+ if (!i)
+ return FALSE;
+ j = tsmf_codec_parse_BITMAPINFOHEADER(mediatype, s, TRUE);
+ if (!j)
+ return FALSE;
+ i += j;
+
+ if (cbFormat > i)
+ {
+ mediatype->ExtraDataSize = cbFormat - i;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, mediatype->ExtraDataSize))
+ return FALSE;
+ mediatype->ExtraData = Stream_Pointer(s);
+ }
+ break;
+
+ case TSMF_FORMAT_TYPE_MPEG2VIDEOINFO:
+ /* http://msdn.microsoft.com/en-us/library/dd390707.aspx */
+ i = tsmf_codec_parse_VIDEOINFOHEADER2(mediatype, s);
+ if (!i)
+ return FALSE;
+ j = tsmf_codec_parse_BITMAPINFOHEADER(mediatype, s, TRUE);
+ if (!j)
+ return FALSE;
+ i += j;
+
+ if (cbFormat > i)
+ {
+ mediatype->ExtraDataSize = cbFormat - i;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, mediatype->ExtraDataSize))
+ return FALSE;
+ mediatype->ExtraData = Stream_Pointer(s);
+ }
+ break;
+
+ case TSMF_FORMAT_TYPE_VIDEOINFO2:
+ i = tsmf_codec_parse_VIDEOINFOHEADER2(mediatype, s);
+ if (!i)
+ return FALSE;
+ j = tsmf_codec_parse_BITMAPINFOHEADER(mediatype, s, FALSE);
+ if (!j)
+ return FALSE;
+ i += j;
+
+ if (cbFormat > i)
+ {
+ mediatype->ExtraDataSize = cbFormat - i;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, mediatype->ExtraDataSize))
+ return FALSE;
+ mediatype->ExtraData = Stream_Pointer(s);
+ }
+ break;
+
+ default:
+ WLog_INFO(TAG, "unhandled format type 0x%x", mediatype->FormatType);
+ break;
+ }
+ return TRUE;
+}
+
+BOOL tsmf_codec_parse_media_type(TS_AM_MEDIA_TYPE* mediatype, wStream* s)
+{
+ UINT32 cbFormat = 0;
+ BOOL ret = TRUE;
+
+ ZeroMemory(mediatype, sizeof(TS_AM_MEDIA_TYPE));
+
+ /* MajorType */
+ DEBUG_TSMF("MediaMajorType:");
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 16))
+ return FALSE;
+ tsmf_print_guid(Stream_Pointer(s));
+
+ size_t i = 0;
+ for (; tsmf_major_type_map[i].type != TSMF_MAJOR_TYPE_UNKNOWN; i++)
+ {
+ if (memcmp(tsmf_major_type_map[i].guid, Stream_Pointer(s), 16) == 0)
+ break;
+ }
+
+ mediatype->MajorType = tsmf_major_type_map[i].type;
+ if (mediatype->MajorType == TSMF_MAJOR_TYPE_UNKNOWN)
+ ret = FALSE;
+
+ DEBUG_TSMF("MediaMajorType %s", tsmf_major_type_map[i].name);
+ Stream_Seek(s, 16);
+
+ /* SubType */
+ DEBUG_TSMF("MediaSubType:");
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 16))
+ return FALSE;
+ tsmf_print_guid(Stream_Pointer(s));
+
+ for (i = 0; tsmf_sub_type_map[i].type != TSMF_SUB_TYPE_UNKNOWN; i++)
+ {
+ if (memcmp(tsmf_sub_type_map[i].guid, Stream_Pointer(s), 16) == 0)
+ break;
+ }
+
+ mediatype->SubType = tsmf_sub_type_map[i].type;
+ if (mediatype->SubType == TSMF_SUB_TYPE_UNKNOWN)
+ ret = FALSE;
+
+ DEBUG_TSMF("MediaSubType %s", tsmf_sub_type_map[i].name);
+ Stream_Seek(s, 16);
+
+ /* bFixedSizeSamples, bTemporalCompression, SampleSize */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return FALSE;
+ Stream_Seek(s, 12);
+
+ /* FormatType */
+ DEBUG_TSMF("FormatType:");
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 16))
+ return FALSE;
+ tsmf_print_guid(Stream_Pointer(s));
+
+ for (i = 0; tsmf_format_type_map[i].type != TSMF_FORMAT_TYPE_UNKNOWN; i++)
+ {
+ if (memcmp(tsmf_format_type_map[i].guid, Stream_Pointer(s), 16) == 0)
+ break;
+ }
+
+ mediatype->FormatType = tsmf_format_type_map[i].type;
+ if (mediatype->FormatType == TSMF_FORMAT_TYPE_UNKNOWN)
+ ret = FALSE;
+
+ DEBUG_TSMF("FormatType %s", tsmf_format_type_map[i].name);
+ Stream_Seek(s, 16);
+
+ /* cbFormat */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+ Stream_Read_UINT32(s, cbFormat);
+ DEBUG_TSMF("cbFormat %" PRIu32 "", cbFormat);
+#ifdef WITH_DEBUG_TSMF
+ winpr_HexDump(TAG, WLOG_DEBUG, Stream_Pointer(s), cbFormat);
+#endif
+
+ ret = tsmf_read_format_type(mediatype, s, cbFormat);
+
+ if (mediatype->SamplesPerSecond.Numerator == 0)
+ mediatype->SamplesPerSecond.Numerator = 1;
+
+ if (mediatype->SamplesPerSecond.Denominator == 0)
+ mediatype->SamplesPerSecond.Denominator = 1;
+
+ return ret;
+}
+
+BOOL tsmf_codec_check_media_type(const char* decoder_name, wStream* s)
+{
+ size_t pos = 0;
+ BOOL ret = FALSE;
+ TS_AM_MEDIA_TYPE mediatype;
+
+ static BOOL decoderAvailable = FALSE;
+ static BOOL firstRun = TRUE;
+
+ if (firstRun)
+ {
+ firstRun = FALSE;
+ if (tsmf_check_decoder_available(decoder_name))
+ decoderAvailable = TRUE;
+ }
+
+ pos = Stream_GetPosition(s);
+ if (decoderAvailable)
+ ret = tsmf_codec_parse_media_type(&mediatype, s);
+ Stream_SetPosition(s, pos);
+
+ if (ret)
+ {
+ ITSMFDecoder* decoder = tsmf_load_decoder(decoder_name, &mediatype);
+
+ if (!decoder)
+ {
+ WLog_WARN(TAG, "Format not supported by decoder %s", decoder_name);
+ ret = FALSE;
+ }
+ else
+ {
+ decoder->Free(decoder);
+ }
+ }
+
+ return ret;
+}
diff --git a/channels/tsmf/client/tsmf_codec.h b/channels/tsmf/client/tsmf_codec.h
new file mode 100644
index 0000000..ab98899
--- /dev/null
+++ b/channels/tsmf/client/tsmf_codec.h
@@ -0,0 +1,28 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - Codec
+ *
+ * Copyright 2010-2011 Vic Lee
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_TSMF_CLIENT_CODEC_H
+#define FREERDP_CHANNEL_TSMF_CLIENT_CODEC_H
+
+#include "tsmf_types.h"
+
+BOOL tsmf_codec_parse_media_type(TS_AM_MEDIA_TYPE* mediatype, wStream* s);
+BOOL tsmf_codec_check_media_type(const char* decoder_name, wStream* s);
+
+#endif /* FREERDP_CHANNEL_TSMF_CLIENT_CODEC_H */
diff --git a/channels/tsmf/client/tsmf_constants.h b/channels/tsmf/client/tsmf_constants.h
new file mode 100644
index 0000000..43d37f2
--- /dev/null
+++ b/channels/tsmf/client/tsmf_constants.h
@@ -0,0 +1,139 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - Constants
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2012 Hewlett-Packard Development Company, L.P.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_TSMF_CLIENT_CONSTANTS_H
+#define FREERDP_CHANNEL_TSMF_CLIENT_CONSTANTS_H
+
+#define GUID_SIZE 16
+#define TSMF_BUFFER_PADDING_SIZE 8
+
+/* Interface IDs defined in [MS-RDPEV]. There's no constant names in the MS
+ documentation, so we create them on our own. */
+#define TSMF_INTERFACE_DEFAULT 0x00000000
+#define TSMF_INTERFACE_CLIENT_NOTIFICATIONS 0x00000001
+#define TSMF_INTERFACE_CAPABILITIES 0x00000002
+
+/* Interface ID Mask */
+#define STREAM_ID_STUB 0x80000000
+#define STREAM_ID_PROXY 0x40000000
+#define STREAM_ID_NONE 0x00000000
+
+/* Functon ID */
+/* Common IDs for all interfaces are as follows. */
+#define RIMCALL_RELEASE 0x00000001
+#define RIMCALL_QUERYINTERFACE 0x00000002
+/* Capabilities Negotiator Interface IDs are as follows. */
+#define RIM_EXCHANGE_CAPABILITY_REQUEST 0x00000100
+/* The Client Notifications Interface ID is as follows. */
+#define PLAYBACK_ACK 0x00000100
+#define CLIENT_EVENT_NOTIFICATION 0x00000101
+/* Server Data Interface IDs are as follows. */
+#define EXCHANGE_CAPABILITIES_REQ 0x00000100
+#define SET_CHANNEL_PARAMS 0x00000101
+#define ADD_STREAM 0x00000102
+#define ON_SAMPLE 0x00000103
+#define SET_VIDEO_WINDOW 0x00000104
+#define ON_NEW_PRESENTATION 0x00000105
+#define SHUTDOWN_PRESENTATION_REQ 0x00000106
+#define SET_TOPOLOGY_REQ 0x00000107
+#define CHECK_FORMAT_SUPPORT_REQ 0x00000108
+#define ON_PLAYBACK_STARTED 0x00000109
+#define ON_PLAYBACK_PAUSED 0x0000010a
+#define ON_PLAYBACK_STOPPED 0x0000010b
+#define ON_PLAYBACK_RESTARTED 0x0000010c
+#define ON_PLAYBACK_RATE_CHANGED 0x0000010d
+#define ON_FLUSH 0x0000010e
+#define ON_STREAM_VOLUME 0x0000010f
+#define ON_CHANNEL_VOLUME 0x00000110
+#define ON_END_OF_STREAM 0x00000111
+#define SET_ALLOCATOR 0x00000112
+#define NOTIFY_PREROLL 0x00000113
+#define UPDATE_GEOMETRY_INFO 0x00000114
+#define REMOVE_STREAM 0x00000115
+#define SET_SOURCE_VIDEO_RECT 0x00000116
+
+/* Supported platform */
+#define MMREDIR_CAPABILITY_PLATFORM_MF 0x00000001
+#define MMREDIR_CAPABILITY_PLATFORM_DSHOW 0x00000002
+#define MMREDIR_CAPABILITY_PLATFORM_OTHER 0x00000004
+
+/* TSMM_CLIENT_EVENT Constants */
+#define TSMM_CLIENT_EVENT_ENDOFSTREAM 0x0064
+#define TSMM_CLIENT_EVENT_STOP_COMPLETED 0x00C8
+#define TSMM_CLIENT_EVENT_START_COMPLETED 0x00C9
+#define TSMM_CLIENT_EVENT_MONITORCHANGED 0x012C
+
+/* TS_MM_DATA_SAMPLE.SampleExtensions */
+#define TSMM_SAMPLE_EXT_CLEANPOINT 0x00000001
+#define TSMM_SAMPLE_EXT_DISCONTINUITY 0x00000002
+#define TSMM_SAMPLE_EXT_INTERLACED 0x00000004
+#define TSMM_SAMPLE_EXT_BOTTOMFIELDFIRST 0x00000008
+#define TSMM_SAMPLE_EXT_REPEATFIELDFIRST 0x00000010
+#define TSMM_SAMPLE_EXT_SINGLEFIELD 0x00000020
+#define TSMM_SAMPLE_EXT_DERIVEDFROMTOPFIELD 0x00000040
+#define TSMM_SAMPLE_EXT_HAS_NO_TIMESTAMPS 0x00000080
+#define TSMM_SAMPLE_EXT_RELATIVE_TIMESTAMPS 0x00000100
+#define TSMM_SAMPLE_EXT_ABSOLUTE_TIMESTAMPS 0x00000200
+
+/* MajorType */
+#define TSMF_MAJOR_TYPE_UNKNOWN 0
+#define TSMF_MAJOR_TYPE_VIDEO 1
+#define TSMF_MAJOR_TYPE_AUDIO 2
+
+/* SubType */
+#define TSMF_SUB_TYPE_UNKNOWN 0
+#define TSMF_SUB_TYPE_WVC1 1
+#define TSMF_SUB_TYPE_WMA2 2
+#define TSMF_SUB_TYPE_WMA9 3
+#define TSMF_SUB_TYPE_MP3 4
+#define TSMF_SUB_TYPE_MP2A 5
+#define TSMF_SUB_TYPE_MP2V 6
+#define TSMF_SUB_TYPE_WMV3 7
+#define TSMF_SUB_TYPE_AAC 8
+#define TSMF_SUB_TYPE_H264 9
+#define TSMF_SUB_TYPE_AVC1 10
+#define TSMF_SUB_TYPE_AC3 11
+#define TSMF_SUB_TYPE_WMV2 12
+#define TSMF_SUB_TYPE_WMV1 13
+#define TSMF_SUB_TYPE_MP1V 14
+#define TSMF_SUB_TYPE_MP1A 15
+#define TSMF_SUB_TYPE_YUY2 16
+#define TSMF_SUB_TYPE_MP43 17
+#define TSMF_SUB_TYPE_MP4S 18
+#define TSMF_SUB_TYPE_MP42 19
+#define TSMF_SUB_TYPE_OGG 20
+#define TSMF_SUB_TYPE_SPEEX 21
+#define TSMF_SUB_TYPE_THEORA 22
+#define TSMF_SUB_TYPE_FLAC 23
+#define TSMF_SUB_TYPE_VP8 24
+#define TSMF_SUB_TYPE_VP9 25
+#define TSMF_SUB_TYPE_H263 26
+#define TSMF_SUB_TYPE_M4S2 27
+#define TSMF_SUB_TYPE_WMA1 28
+
+/* FormatType */
+#define TSMF_FORMAT_TYPE_UNKNOWN 0
+#define TSMF_FORMAT_TYPE_MFVIDEOFORMAT 1
+#define TSMF_FORMAT_TYPE_WAVEFORMATEX 2
+#define TSMF_FORMAT_TYPE_MPEG2VIDEOINFO 3
+#define TSMF_FORMAT_TYPE_VIDEOINFO2 4
+#define TSMF_FORMAT_TYPE_MPEG1VIDEOINFO 5
+
+#endif /* FREERDP_CHANNEL_TSMF_CLIENT_CONSTANTS_H */
diff --git a/channels/tsmf/client/tsmf_decoder.c b/channels/tsmf/client/tsmf_decoder.c
new file mode 100644
index 0000000..0e318f7
--- /dev/null
+++ b/channels/tsmf/client/tsmf_decoder.c
@@ -0,0 +1,120 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - Decoder
+ *
+ * Copyright 2010-2011 Vic Lee
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <freerdp/addin.h>
+#include <freerdp/client/channels.h>
+
+#include "tsmf_types.h"
+#include "tsmf_constants.h"
+#include "tsmf_decoder.h"
+
+static ITSMFDecoder* tsmf_load_decoder_by_name(const char* name)
+{
+ ITSMFDecoder* decoder = NULL;
+ TSMF_DECODER_ENTRY entry = NULL;
+
+ entry = (TSMF_DECODER_ENTRY)(void*)freerdp_load_channel_addin_entry("tsmf", name, "decoder", 0);
+
+ if (!entry)
+ return NULL;
+
+ decoder = entry();
+
+ if (!decoder)
+ {
+ WLog_ERR(TAG, "failed to call export function in %s", name);
+ return NULL;
+ }
+
+ return decoder;
+}
+
+static BOOL tsmf_decoder_set_format(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type)
+{
+ if (decoder->SetFormat(decoder, media_type))
+ return TRUE;
+ else
+ return FALSE;
+}
+
+ITSMFDecoder* tsmf_load_decoder(const char* name, TS_AM_MEDIA_TYPE* media_type)
+{
+ ITSMFDecoder* decoder = NULL;
+
+ if (name)
+ {
+ decoder = tsmf_load_decoder_by_name(name);
+ }
+
+#if defined(WITH_GSTREAMER_1_0)
+ if (!decoder)
+ decoder = tsmf_load_decoder_by_name("gstreamer");
+#endif
+
+#if defined(WITH_VIDEO_FFMPEG)
+ if (!decoder)
+ decoder = tsmf_load_decoder_by_name("ffmpeg");
+#endif
+
+ if (decoder)
+ {
+ if (!tsmf_decoder_set_format(decoder, media_type))
+ {
+ decoder->Free(decoder);
+ decoder = NULL;
+ }
+ }
+
+ return decoder;
+}
+
+BOOL tsmf_check_decoder_available(const char* name)
+{
+ ITSMFDecoder* decoder = NULL;
+ BOOL retValue = FALSE;
+
+ if (name)
+ {
+ decoder = tsmf_load_decoder_by_name(name);
+ }
+#if defined(WITH_GSTREAMER_1_0)
+ if (!decoder)
+ decoder = tsmf_load_decoder_by_name("gstreamer");
+#endif
+
+#if defined(WITH_VIDEO_FFMPEG)
+ if (!decoder)
+ decoder = tsmf_load_decoder_by_name("ffmpeg");
+#endif
+
+ if (decoder)
+ {
+ decoder->Free(decoder);
+ decoder = NULL;
+ retValue = TRUE;
+ }
+
+ return retValue;
+}
diff --git a/channels/tsmf/client/tsmf_decoder.h b/channels/tsmf/client/tsmf_decoder.h
new file mode 100644
index 0000000..6b7833e
--- /dev/null
+++ b/channels/tsmf/client/tsmf_decoder.h
@@ -0,0 +1,78 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - Decoder
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2012 Hewlett-Packard Development Company, L.P.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_TSMF_CLIENT_DECODER_H
+#define FREERDP_CHANNEL_TSMF_CLIENT_DECODER_H
+
+#include "tsmf_types.h"
+
+typedef enum
+{
+ Control_Pause,
+ Control_Resume,
+ Control_Restart,
+ Control_Stop
+} ITSMFControlMsg;
+
+typedef struct s_ITSMFDecoder ITSMFDecoder;
+
+struct s_ITSMFDecoder
+{
+ /* Set the decoder format. Return true if supported. */
+ BOOL (*SetFormat)(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type);
+ /* Decode a sample. */
+ BOOL (*Decode)(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size, UINT32 extensions);
+ /* Get the decoded data */
+ BYTE* (*GetDecodedData)(ITSMFDecoder* decoder, UINT32* size);
+ /* Get the pixel format of decoded video frame */
+ UINT32 (*GetDecodedFormat)(ITSMFDecoder* decoder);
+ /* Get the width and height of decoded video frame */
+ BOOL (*GetDecodedDimension)(ITSMFDecoder* decoder, UINT32* width, UINT32* height);
+ /* Free the decoder */
+ void (*Free)(ITSMFDecoder* decoder);
+ /* Optional Contol function */
+ BOOL (*Control)(ITSMFDecoder* decoder, ITSMFControlMsg control_msg, UINT32* arg);
+ /* Decode a sample with extended interface. */
+ BOOL(*DecodeEx)
+ (ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size, UINT32 extensions,
+ UINT64 start_time, UINT64 end_time, UINT64 duration);
+ /* Get current play time */
+ UINT64 (*GetRunningTime)(ITSMFDecoder* decoder);
+ /* Update Gstreamer Rendering Area */
+ BOOL(*UpdateRenderingArea)
+ (ITSMFDecoder* decoder, int newX, int newY, int newWidth, int newHeight, int numRectangles,
+ RDP_RECT* rectangles);
+ /* Change Gstreamer Audio Volume */
+ BOOL (*ChangeVolume)(ITSMFDecoder* decoder, UINT32 newVolume, UINT32 muted);
+ /* Check buffer level */
+ BOOL (*BufferLevel)(ITSMFDecoder* decoder);
+ /* Register a callback for frame ack. */
+ BOOL (*SetAckFunc)(ITSMFDecoder* decoder, BOOL (*cb)(void*, BOOL), void* stream);
+ /* Register a callback for stream seek detection. */
+ BOOL (*SetSyncFunc)(ITSMFDecoder* decoder, void (*cb)(void*), void* stream);
+};
+
+#define TSMF_DECODER_EXPORT_FUNC_NAME "TSMFDecoderEntry"
+typedef ITSMFDecoder* (*TSMF_DECODER_ENTRY)(void);
+
+ITSMFDecoder* tsmf_load_decoder(const char* name, TS_AM_MEDIA_TYPE* media_type);
+BOOL tsmf_check_decoder_available(const char* name);
+
+#endif /* FREERDP_CHANNEL_TSMF_CLIENT_DECODER_H */
diff --git a/channels/tsmf/client/tsmf_ifman.c b/channels/tsmf/client/tsmf_ifman.c
new file mode 100644
index 0000000..2230505
--- /dev/null
+++ b/channels/tsmf/client/tsmf_ifman.c
@@ -0,0 +1,841 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - Interface Manipulation
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2012 Hewlett-Packard Development Company, L.P.
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+
+#include <winpr/stream.h>
+
+#include "tsmf_types.h"
+#include "tsmf_constants.h"
+#include "tsmf_media.h"
+#include "tsmf_codec.h"
+
+#include "tsmf_ifman.h"
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_rim_exchange_capability_request(TSMF_IFMAN* ifman)
+{
+ UINT32 CapabilityValue = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(ifman->input, CapabilityValue);
+ DEBUG_TSMF("server CapabilityValue %" PRIu32 "", CapabilityValue);
+
+ if (!Stream_EnsureRemainingCapacity(ifman->output, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Write_UINT32(ifman->output, 1); /* CapabilityValue */
+ Stream_Write_UINT32(ifman->output, 0); /* Result */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_exchange_capability_request(TSMF_IFMAN* ifman)
+{
+ UINT32 v = 0;
+ UINT32 pos = 0;
+ UINT32 CapabilityType = 0;
+ UINT32 cbCapabilityLength = 0;
+ UINT32 numHostCapabilities = 0;
+
+ WINPR_ASSERT(ifman);
+ if (!Stream_EnsureRemainingCapacity(ifman->output, ifman->input_size + 4))
+ return ERROR_OUTOFMEMORY;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, ifman->input_size))
+ return ERROR_INVALID_DATA;
+
+ pos = Stream_GetPosition(ifman->output);
+ Stream_Copy(ifman->input, ifman->output, ifman->input_size);
+ Stream_SetPosition(ifman->output, pos);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(ifman->output, numHostCapabilities);
+
+ for (UINT32 i = 0; i < numHostCapabilities; i++)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(ifman->output, CapabilityType);
+ Stream_Read_UINT32(ifman->output, cbCapabilityLength);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, cbCapabilityLength))
+ return ERROR_INVALID_DATA;
+
+ pos = Stream_GetPosition(ifman->output);
+
+ switch (CapabilityType)
+ {
+ case 1: /* Protocol version request */
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(ifman->output, v);
+ DEBUG_TSMF("server protocol version %" PRIu32 "", v);
+ break;
+
+ case 2: /* Supported platform */
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Peek_UINT32(ifman->output, v);
+ DEBUG_TSMF("server supported platform %" PRIu32 "", v);
+ /* Claim that we support both MF and DShow platforms. */
+ Stream_Write_UINT32(ifman->output, MMREDIR_CAPABILITY_PLATFORM_MF |
+ MMREDIR_CAPABILITY_PLATFORM_DSHOW);
+ break;
+
+ default:
+ WLog_ERR(TAG, "skipping unknown capability type %" PRIu32 "", CapabilityType);
+ break;
+ }
+
+ Stream_SetPosition(ifman->output, pos + cbCapabilityLength);
+ }
+
+ Stream_Write_UINT32(ifman->output, 0); /* Result */
+ ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_check_format_support_request(TSMF_IFMAN* ifman)
+{
+ UINT32 numMediaType = 0;
+ UINT32 PlatformCookie = 0;
+ UINT32 FormatSupported = 1;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(ifman->input, PlatformCookie);
+ Stream_Seek_UINT32(ifman->input); /* NoRolloverFlags (4 bytes) */
+ Stream_Read_UINT32(ifman->input, numMediaType);
+ DEBUG_TSMF("PlatformCookie %" PRIu32 " numMediaType %" PRIu32 "", PlatformCookie, numMediaType);
+
+ if (!tsmf_codec_check_media_type(ifman->decoder_name, ifman->input))
+ FormatSupported = 0;
+
+ if (FormatSupported)
+ DEBUG_TSMF("format ok.");
+
+ if (!Stream_EnsureRemainingCapacity(ifman->output, 12))
+ return -1;
+
+ Stream_Write_UINT32(ifman->output, FormatSupported);
+ Stream_Write_UINT32(ifman->output, PlatformCookie);
+ Stream_Write_UINT32(ifman->output, 0); /* Result */
+ ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_on_new_presentation(TSMF_IFMAN* ifman)
+{
+ UINT status = CHANNEL_RC_OK;
+ TSMF_PRESENTATION* presentation = NULL;
+ DEBUG_TSMF("");
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE))
+ return ERROR_INVALID_DATA;
+
+ presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));
+
+ if (presentation)
+ {
+ DEBUG_TSMF("Presentation already exists");
+ ifman->output_pending = FALSE;
+ return CHANNEL_RC_OK;
+ }
+
+ presentation = tsmf_presentation_new(Stream_Pointer(ifman->input), ifman->channel_callback);
+
+ if (!presentation)
+ status = ERROR_OUTOFMEMORY;
+ else
+ tsmf_presentation_set_audio_device(presentation, ifman->audio_name, ifman->audio_device);
+
+ ifman->output_pending = TRUE;
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_add_stream(TSMF_IFMAN* ifman, rdpContext* rdpcontext)
+{
+ UINT32 StreamId = 0;
+ UINT status = CHANNEL_RC_OK;
+ TSMF_STREAM* stream = NULL;
+ TSMF_PRESENTATION* presentation = NULL;
+ DEBUG_TSMF("");
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE + 8))
+ return ERROR_INVALID_DATA;
+
+ presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));
+ Stream_Seek(ifman->input, GUID_SIZE);
+
+ if (!presentation)
+ {
+ WLog_ERR(TAG, "unknown presentation id");
+ status = ERROR_NOT_FOUND;
+ }
+ else
+ {
+ Stream_Read_UINT32(ifman->input, StreamId);
+ Stream_Seek_UINT32(ifman->input); /* numMediaType */
+ stream = tsmf_stream_new(presentation, StreamId, rdpcontext);
+
+ if (!stream)
+ {
+ WLog_ERR(TAG, "failed to create stream");
+ return ERROR_OUTOFMEMORY;
+ }
+
+ if (!tsmf_stream_set_format(stream, ifman->decoder_name, ifman->input))
+ {
+ WLog_ERR(TAG, "failed to set stream format");
+ return ERROR_OUTOFMEMORY;
+ }
+
+ tsmf_stream_start_threads(stream);
+ }
+
+ ifman->output_pending = TRUE;
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_set_topology_request(TSMF_IFMAN* ifman)
+{
+ DEBUG_TSMF("");
+
+ if (!Stream_EnsureRemainingCapacity(ifman->output, 8))
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write_UINT32(ifman->output, 1); /* TopologyReady */
+ Stream_Write_UINT32(ifman->output, 0); /* Result */
+ ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_remove_stream(TSMF_IFMAN* ifman)
+{
+ int status = CHANNEL_RC_OK;
+ UINT32 StreamId = 0;
+ TSMF_STREAM* stream = NULL;
+ TSMF_PRESENTATION* presentation = NULL;
+ DEBUG_TSMF("");
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 20))
+ return ERROR_INVALID_DATA;
+
+ presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));
+ Stream_Seek(ifman->input, GUID_SIZE);
+
+ if (!presentation)
+ {
+ status = ERROR_NOT_FOUND;
+ }
+ else
+ {
+ Stream_Read_UINT32(ifman->input, StreamId);
+ stream = tsmf_stream_find_by_id(presentation, StreamId);
+
+ if (stream)
+ tsmf_stream_free(stream);
+ else
+ status = ERROR_NOT_FOUND;
+ }
+
+ ifman->output_pending = TRUE;
+ return status;
+}
+
+static float tsmf_stream_read_float(wStream* s)
+{
+ float fValue = NAN;
+ UINT32 iValue = 0;
+ Stream_Read_UINT32(s, iValue);
+ CopyMemory(&fValue, &iValue, 4);
+ return fValue;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_set_source_video_rect(TSMF_IFMAN* ifman)
+{
+ UINT status = CHANNEL_RC_OK;
+ float Left = NAN;
+ float Top = NAN;
+ float Right = NAN;
+ float Bottom = NAN;
+ TSMF_PRESENTATION* presentation = NULL;
+ DEBUG_TSMF("");
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 32))
+ return ERROR_INVALID_DATA;
+
+ presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));
+ Stream_Seek(ifman->input, GUID_SIZE);
+
+ if (!presentation)
+ {
+ status = ERROR_NOT_FOUND;
+ }
+ else
+ {
+ Left = tsmf_stream_read_float(ifman->input); /* Left (4 bytes) */
+ Top = tsmf_stream_read_float(ifman->input); /* Top (4 bytes) */
+ Right = tsmf_stream_read_float(ifman->input); /* Right (4 bytes) */
+ Bottom = tsmf_stream_read_float(ifman->input); /* Bottom (4 bytes) */
+ DEBUG_TSMF("SetSourceVideoRect: Left: %f Top: %f Right: %f Bottom: %f", Left, Top, Right,
+ Bottom);
+ }
+
+ ifman->output_pending = TRUE;
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_shutdown_presentation(TSMF_IFMAN* ifman)
+{
+ TSMF_PRESENTATION* presentation = NULL;
+ DEBUG_TSMF("");
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE))
+ return ERROR_INVALID_DATA;
+
+ presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));
+
+ if (presentation)
+ tsmf_presentation_free(presentation);
+ else
+ {
+ WLog_ERR(TAG, "unknown presentation id");
+ return ERROR_NOT_FOUND;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(ifman->output, 4))
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write_UINT32(ifman->output, 0); /* Result */
+ ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_on_stream_volume(TSMF_IFMAN* ifman)
+{
+ TSMF_PRESENTATION* presentation = NULL;
+ UINT32 newVolume = 0;
+ UINT32 muted = 0;
+ DEBUG_TSMF("on stream volume");
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE + 8))
+ return ERROR_INVALID_DATA;
+
+ presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));
+
+ if (!presentation)
+ {
+ WLog_ERR(TAG, "unknown presentation id");
+ return ERROR_NOT_FOUND;
+ }
+
+ Stream_Seek(ifman->input, 16);
+ Stream_Read_UINT32(ifman->input, newVolume);
+ DEBUG_TSMF("on stream volume: new volume=[%" PRIu32 "]", newVolume);
+ Stream_Read_UINT32(ifman->input, muted);
+ DEBUG_TSMF("on stream volume: muted=[%" PRIu32 "]", muted);
+
+ if (!tsmf_presentation_volume_changed(presentation, newVolume, muted))
+ return ERROR_INVALID_OPERATION;
+
+ ifman->output_pending = TRUE;
+ return 0;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_on_channel_volume(TSMF_IFMAN* ifman)
+{
+ TSMF_PRESENTATION* presentation = NULL;
+ DEBUG_TSMF("on channel volume");
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE + 8))
+ return ERROR_INVALID_DATA;
+
+ presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));
+
+ if (presentation)
+ {
+ UINT32 channelVolume = 0;
+ UINT32 changedChannel = 0;
+ Stream_Seek(ifman->input, 16);
+ Stream_Read_UINT32(ifman->input, channelVolume);
+ DEBUG_TSMF("on channel volume: channel volume=[%" PRIu32 "]", channelVolume);
+ Stream_Read_UINT32(ifman->input, changedChannel);
+ DEBUG_TSMF("on stream volume: changed channel=[%" PRIu32 "]", changedChannel);
+ }
+
+ ifman->output_pending = TRUE;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_set_video_window(TSMF_IFMAN* ifman)
+{
+ DEBUG_TSMF("");
+ ifman->output_pending = TRUE;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_update_geometry_info(TSMF_IFMAN* ifman)
+{
+ TSMF_PRESENTATION* presentation = NULL;
+ UINT32 numGeometryInfo = 0;
+ UINT32 Left = 0;
+ UINT32 Top = 0;
+ UINT32 Width = 0;
+ UINT32 Height = 0;
+ UINT32 cbVisibleRect = 0;
+ RDP_RECT* rects = NULL;
+ int num_rects = 0;
+ UINT error = CHANNEL_RC_OK;
+ int i = 0;
+ size_t pos = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE + 32))
+ return ERROR_INVALID_DATA;
+
+ presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));
+
+ if (!presentation)
+ return ERROR_NOT_FOUND;
+
+ Stream_Seek(ifman->input, 16);
+ Stream_Read_UINT32(ifman->input, numGeometryInfo);
+ pos = Stream_GetPosition(ifman->input);
+ Stream_Seek(ifman->input, 12); /* VideoWindowId (8 bytes), VideoWindowState (4 bytes) */
+ Stream_Read_UINT32(ifman->input, Width);
+ Stream_Read_UINT32(ifman->input, Height);
+ Stream_Read_UINT32(ifman->input, Left);
+ Stream_Read_UINT32(ifman->input, Top);
+ Stream_SetPosition(ifman->input, pos + numGeometryInfo);
+ Stream_Read_UINT32(ifman->input, cbVisibleRect);
+ num_rects = cbVisibleRect / 16;
+ DEBUG_TSMF("numGeometryInfo %" PRIu32 " Width %" PRIu32 " Height %" PRIu32 " Left %" PRIu32
+ " Top %" PRIu32 " cbVisibleRect %" PRIu32 " num_rects %d",
+ numGeometryInfo, Width, Height, Left, Top, cbVisibleRect, num_rects);
+
+ if (num_rects > 0)
+ {
+ rects = (RDP_RECT*)calloc(num_rects, sizeof(RDP_RECT));
+
+ for (UINT32 i = 0; i < num_rects; i++)
+ {
+ Stream_Read_UINT16(ifman->input, rects[i].y); /* Top */
+ Stream_Seek_UINT16(ifman->input);
+ Stream_Read_UINT16(ifman->input, rects[i].x); /* Left */
+ Stream_Seek_UINT16(ifman->input);
+ Stream_Read_UINT16(ifman->input, rects[i].height); /* Bottom */
+ Stream_Seek_UINT16(ifman->input);
+ Stream_Read_UINT16(ifman->input, rects[i].width); /* Right */
+ Stream_Seek_UINT16(ifman->input);
+ rects[i].width -= rects[i].x;
+ rects[i].height -= rects[i].y;
+ DEBUG_TSMF("rect %d: %" PRId16 " %" PRId16 " %" PRId16 " %" PRId16 "", i, rects[i].x,
+ rects[i].y, rects[i].width, rects[i].height);
+ }
+ }
+
+ if (!tsmf_presentation_set_geometry_info(presentation, Left, Top, Width, Height, num_rects,
+ rects))
+ return ERROR_INVALID_OPERATION;
+
+ ifman->output_pending = TRUE;
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_set_allocator(TSMF_IFMAN* ifman)
+{
+ DEBUG_TSMF("");
+ ifman->output_pending = TRUE;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_notify_preroll(TSMF_IFMAN* ifman)
+{
+ DEBUG_TSMF("");
+ tsmf_ifman_on_playback_paused(ifman);
+ ifman->output_pending = TRUE;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_on_sample(TSMF_IFMAN* ifman)
+{
+ TSMF_PRESENTATION* presentation = NULL;
+ TSMF_STREAM* stream = NULL;
+ UINT32 StreamId = 0;
+ UINT64 SampleStartTime = 0;
+ UINT64 SampleEndTime = 0;
+ UINT64 ThrottleDuration = 0;
+ UINT32 SampleExtensions = 0;
+ UINT32 cbData = 0;
+ UINT error = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 60))
+ return ERROR_INVALID_DATA;
+
+ Stream_Seek(ifman->input, 16);
+ Stream_Read_UINT32(ifman->input, StreamId);
+ Stream_Seek_UINT32(ifman->input); /* numSample */
+ Stream_Read_UINT64(ifman->input, SampleStartTime);
+ Stream_Read_UINT64(ifman->input, SampleEndTime);
+ Stream_Read_UINT64(ifman->input, ThrottleDuration);
+ Stream_Seek_UINT32(ifman->input); /* SampleFlags */
+ Stream_Read_UINT32(ifman->input, SampleExtensions);
+ Stream_Read_UINT32(ifman->input, cbData);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, cbData))
+ return ERROR_INVALID_DATA;
+
+ DEBUG_TSMF("MessageId %" PRIu32 " StreamId %" PRIu32 " SampleStartTime %" PRIu64
+ " SampleEndTime %" PRIu64 " "
+ "ThrottleDuration %" PRIu64 " SampleExtensions %" PRIu32 " cbData %" PRIu32 "",
+ ifman->message_id, StreamId, SampleStartTime, SampleEndTime, ThrottleDuration,
+ SampleExtensions, cbData);
+ presentation = tsmf_presentation_find_by_id(ifman->presentation_id);
+
+ if (!presentation)
+ {
+ WLog_ERR(TAG, "unknown presentation id");
+ return ERROR_NOT_FOUND;
+ }
+
+ stream = tsmf_stream_find_by_id(presentation, StreamId);
+
+ if (!stream)
+ {
+ WLog_ERR(TAG, "unknown stream id");
+ return ERROR_NOT_FOUND;
+ }
+
+ if (!tsmf_stream_push_sample(stream, ifman->channel_callback, ifman->message_id,
+ SampleStartTime, SampleEndTime, ThrottleDuration, SampleExtensions,
+ cbData, Stream_Pointer(ifman->input)))
+ {
+ WLog_ERR(TAG, "unable to push sample");
+ return ERROR_OUTOFMEMORY;
+ }
+
+ if ((error = tsmf_presentation_sync(presentation)))
+ {
+ WLog_ERR(TAG, "tsmf_presentation_sync failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ ifman->output_pending = TRUE;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_on_flush(TSMF_IFMAN* ifman)
+{
+ UINT32 StreamId = 0;
+ TSMF_PRESENTATION* presentation = NULL;
+ TSMF_STREAM* stream = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 20))
+ return ERROR_INVALID_DATA;
+
+ Stream_Seek(ifman->input, 16);
+ Stream_Read_UINT32(ifman->input, StreamId);
+ DEBUG_TSMF("StreamId %" PRIu32 "", StreamId);
+ presentation = tsmf_presentation_find_by_id(ifman->presentation_id);
+
+ if (!presentation)
+ {
+ WLog_ERR(TAG, "unknown presentation id");
+ return ERROR_NOT_FOUND;
+ }
+
+ /* Flush message is for a stream, not the entire presentation
+ * therefore we only flush the stream as intended per the MS-RDPEV spec
+ */
+ stream = tsmf_stream_find_by_id(presentation, StreamId);
+
+ if (stream)
+ {
+ if (!tsmf_stream_flush(stream))
+ return ERROR_INVALID_OPERATION;
+ }
+ else
+ WLog_ERR(TAG, "unknown stream id");
+
+ ifman->output_pending = TRUE;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_on_end_of_stream(TSMF_IFMAN* ifman)
+{
+ UINT32 StreamId = 0;
+ TSMF_STREAM* stream = NULL;
+ TSMF_PRESENTATION* presentation = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 20))
+ return ERROR_INVALID_DATA;
+
+ presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));
+ Stream_Seek(ifman->input, 16);
+ Stream_Read_UINT32(ifman->input, StreamId);
+
+ if (presentation)
+ {
+ stream = tsmf_stream_find_by_id(presentation, StreamId);
+
+ if (stream)
+ tsmf_stream_end(stream, ifman->message_id, ifman->channel_callback);
+ }
+
+ DEBUG_TSMF("StreamId %" PRIu32 "", StreamId);
+ ifman->output_pending = TRUE;
+ ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_on_playback_started(TSMF_IFMAN* ifman)
+{
+ TSMF_PRESENTATION* presentation = NULL;
+ DEBUG_TSMF("");
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 16))
+ return ERROR_INVALID_DATA;
+
+ presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));
+
+ if (presentation)
+ tsmf_presentation_start(presentation);
+ else
+ WLog_ERR(TAG, "unknown presentation id");
+
+ if (!Stream_EnsureRemainingCapacity(ifman->output, 16))
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write_UINT32(ifman->output, CLIENT_EVENT_NOTIFICATION); /* FunctionId */
+ Stream_Write_UINT32(ifman->output, 0); /* StreamId */
+ Stream_Write_UINT32(ifman->output, TSMM_CLIENT_EVENT_START_COMPLETED); /* EventId */
+ Stream_Write_UINT32(ifman->output, 0); /* cbData */
+ ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_on_playback_paused(TSMF_IFMAN* ifman)
+{
+ TSMF_PRESENTATION* presentation = NULL;
+ DEBUG_TSMF("");
+ ifman->output_pending = TRUE;
+ /* Added pause control so gstreamer pipeline can be paused accordingly */
+ presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));
+
+ if (presentation)
+ {
+ if (!tsmf_presentation_paused(presentation))
+ return ERROR_INVALID_OPERATION;
+ }
+ else
+ WLog_ERR(TAG, "unknown presentation id");
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_on_playback_restarted(TSMF_IFMAN* ifman)
+{
+ TSMF_PRESENTATION* presentation = NULL;
+ DEBUG_TSMF("");
+ ifman->output_pending = TRUE;
+ /* Added restart control so gstreamer pipeline can be resumed accordingly */
+ presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));
+
+ if (presentation)
+ {
+ if (!tsmf_presentation_restarted(presentation))
+ return ERROR_INVALID_OPERATION;
+ }
+ else
+ WLog_ERR(TAG, "unknown presentation id");
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_on_playback_stopped(TSMF_IFMAN* ifman)
+{
+ TSMF_PRESENTATION* presentation = NULL;
+ DEBUG_TSMF("");
+ presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));
+
+ if (presentation)
+ {
+ if (!tsmf_presentation_stop(presentation))
+ return ERROR_INVALID_OPERATION;
+ }
+ else
+ WLog_ERR(TAG, "unknown presentation id");
+
+ if (!Stream_EnsureRemainingCapacity(ifman->output, 16))
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write_UINT32(ifman->output, CLIENT_EVENT_NOTIFICATION); /* FunctionId */
+ Stream_Write_UINT32(ifman->output, 0); /* StreamId */
+ Stream_Write_UINT32(ifman->output, TSMM_CLIENT_EVENT_STOP_COMPLETED); /* EventId */
+ Stream_Write_UINT32(ifman->output, 0); /* cbData */
+ ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_ifman_on_playback_rate_changed(TSMF_IFMAN* ifman)
+{
+ DEBUG_TSMF("");
+
+ if (!Stream_EnsureRemainingCapacity(ifman->output, 16))
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write_UINT32(ifman->output, CLIENT_EVENT_NOTIFICATION); /* FunctionId */
+ Stream_Write_UINT32(ifman->output, 0); /* StreamId */
+ Stream_Write_UINT32(ifman->output, TSMM_CLIENT_EVENT_MONITORCHANGED); /* EventId */
+ Stream_Write_UINT32(ifman->output, 0); /* cbData */
+ ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY;
+ return CHANNEL_RC_OK;
+}
diff --git a/channels/tsmf/client/tsmf_ifman.h b/channels/tsmf/client/tsmf_ifman.h
new file mode 100644
index 0000000..9547f6b
--- /dev/null
+++ b/channels/tsmf/client/tsmf_ifman.h
@@ -0,0 +1,68 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - Interface Manipulation
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_TSMF_CLIENT_IFMAN_H
+#define FREERDP_CHANNEL_TSMF_CLIENT_IFMAN_H
+
+#include <freerdp/freerdp.h>
+
+typedef struct
+{
+ IWTSVirtualChannelCallback* channel_callback;
+ const char* decoder_name;
+ const char* audio_name;
+ const char* audio_device;
+ BYTE presentation_id[16];
+ UINT32 stream_id;
+ UINT32 message_id;
+
+ wStream* input;
+ UINT32 input_size;
+ wStream* output;
+ BOOL output_pending;
+ UINT32 output_interface_id;
+} TSMF_IFMAN;
+
+UINT tsmf_ifman_rim_exchange_capability_request(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_exchange_capability_request(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_check_format_support_request(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_on_new_presentation(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_add_stream(TSMF_IFMAN* ifman, rdpContext* rdpcontext);
+UINT tsmf_ifman_set_topology_request(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_remove_stream(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_set_source_video_rect(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_shutdown_presentation(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_on_stream_volume(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_on_channel_volume(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_set_video_window(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_update_geometry_info(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_set_allocator(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_notify_preroll(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_on_sample(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_on_flush(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_on_end_of_stream(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_on_playback_started(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_on_playback_paused(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_on_playback_restarted(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_on_playback_stopped(TSMF_IFMAN* ifman);
+UINT tsmf_ifman_on_playback_rate_changed(TSMF_IFMAN* ifman);
+
+#endif /* FREERDP_CHANNEL_TSMF_CLIENT_IFMAN_H */
diff --git a/channels/tsmf/client/tsmf_main.c b/channels/tsmf/client/tsmf_main.c
new file mode 100644
index 0000000..877ef24
--- /dev/null
+++ b/channels/tsmf/client/tsmf_main.c
@@ -0,0 +1,609 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+#include <winpr/cmdline.h>
+
+#include <freerdp/client/tsmf.h>
+
+#include "tsmf_types.h"
+#include "tsmf_constants.h"
+#include "tsmf_ifman.h"
+#include "tsmf_media.h"
+
+#include "tsmf_main.h"
+
+BOOL tsmf_send_eos_response(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id)
+{
+ wStream* s = NULL;
+ int status = -1;
+ TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback;
+
+ if (!callback)
+ {
+ DEBUG_TSMF("No callback reference - unable to send eos response!");
+ return FALSE;
+ }
+
+ if (callback && callback->stream_id && callback->channel && callback->channel->Write)
+ {
+ s = Stream_New(NULL, 24);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT32(s, TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY);
+ Stream_Write_UINT32(s, message_id);
+ Stream_Write_UINT32(s, CLIENT_EVENT_NOTIFICATION); /* FunctionId */
+ Stream_Write_UINT32(s, callback->stream_id); /* StreamId */
+ Stream_Write_UINT32(s, TSMM_CLIENT_EVENT_ENDOFSTREAM); /* EventId */
+ Stream_Write_UINT32(s, 0); /* cbData */
+ DEBUG_TSMF("EOS response size %" PRIuz "", Stream_GetPosition(s));
+ status = callback->channel->Write(callback->channel, Stream_GetPosition(s),
+ Stream_Buffer(s), NULL);
+
+ if (status)
+ {
+ WLog_ERR(TAG, "response error %d", status);
+ }
+
+ Stream_Free(s, TRUE);
+ }
+
+ return (status == 0);
+}
+
+BOOL tsmf_playback_ack(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id,
+ UINT64 duration, UINT32 data_size)
+{
+ wStream* s = NULL;
+ int status = -1;
+ TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback;
+
+ if (!callback)
+ return FALSE;
+
+ s = Stream_New(NULL, 32);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT32(s, TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY);
+ Stream_Write_UINT32(s, message_id);
+ Stream_Write_UINT32(s, PLAYBACK_ACK); /* FunctionId */
+ Stream_Write_UINT32(s, callback->stream_id); /* StreamId */
+ Stream_Write_UINT64(s, duration); /* DataDuration */
+ Stream_Write_UINT64(s, data_size); /* cbData */
+ DEBUG_TSMF("ACK response size %" PRIuz "", Stream_GetPosition(s));
+
+ if (!callback->channel || !callback->channel->Write)
+ {
+ WLog_ERR(TAG, "callback=%p, channel=%p, write=%p", callback,
+ (callback ? callback->channel : NULL),
+ (callback && callback->channel ? callback->channel->Write : NULL));
+ }
+ else
+ {
+ status = callback->channel->Write(callback->channel, Stream_GetPosition(s),
+ Stream_Buffer(s), NULL);
+ }
+
+ if (status)
+ {
+ WLog_ERR(TAG, "response error %d", status);
+ }
+
+ Stream_Free(s, TRUE);
+ return (status == 0);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT tsmf_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
+{
+ size_t length = 0;
+ wStream* input = NULL;
+ wStream* output = NULL;
+ UINT error = CHANNEL_RC_OK;
+ BOOL processed = FALSE;
+ TSMF_IFMAN ifman = { 0 };
+ UINT32 MessageId = 0;
+ UINT32 FunctionId = 0;
+ UINT32 InterfaceId = 0;
+ TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback;
+ UINT32 cbSize = Stream_GetRemainingLength(data);
+
+ /* 2.2.1 Shared Message Header (SHARED_MSG_HEADER) */
+ if (!Stream_CheckAndLogRequiredLength(TAG, data, 12))
+ return ERROR_INVALID_DATA;
+
+ input = data;
+ output = Stream_New(NULL, 256);
+
+ if (!output)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Seek(output, 8);
+ Stream_Read_UINT32(input, InterfaceId); /* InterfaceId (4 bytes) */
+ Stream_Read_UINT32(input, MessageId); /* MessageId (4 bytes) */
+ Stream_Read_UINT32(input, FunctionId); /* FunctionId (4 bytes) */
+ DEBUG_TSMF("cbSize=%" PRIu32 " InterfaceId=0x%" PRIX32 " MessageId=0x%" PRIX32
+ " FunctionId=0x%" PRIX32 "",
+ cbSize, InterfaceId, MessageId, FunctionId);
+ ifman.channel_callback = pChannelCallback;
+ ifman.decoder_name = ((TSMF_PLUGIN*)callback->plugin)->decoder_name;
+ ifman.audio_name = ((TSMF_PLUGIN*)callback->plugin)->audio_name;
+ ifman.audio_device = ((TSMF_PLUGIN*)callback->plugin)->audio_device;
+ CopyMemory(ifman.presentation_id, callback->presentation_id, GUID_SIZE);
+ ifman.stream_id = callback->stream_id;
+ ifman.message_id = MessageId;
+ ifman.input = input;
+ ifman.input_size = cbSize - 12;
+ ifman.output = output;
+ ifman.output_pending = FALSE;
+ ifman.output_interface_id = InterfaceId;
+
+ // fprintf(stderr, "InterfaceId: 0x%08"PRIX32" MessageId: 0x%08"PRIX32" FunctionId:
+ // 0x%08"PRIX32"\n", InterfaceId, MessageId, FunctionId);
+
+ switch (InterfaceId)
+ {
+ case TSMF_INTERFACE_CAPABILITIES | STREAM_ID_NONE:
+ switch (FunctionId)
+ {
+ case RIM_EXCHANGE_CAPABILITY_REQUEST:
+ error = tsmf_ifman_rim_exchange_capability_request(&ifman);
+ processed = TRUE;
+ break;
+
+ case RIMCALL_RELEASE:
+ case RIMCALL_QUERYINTERFACE:
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+
+ case TSMF_INTERFACE_DEFAULT | STREAM_ID_PROXY:
+ switch (FunctionId)
+ {
+ case SET_CHANNEL_PARAMS:
+ if (!Stream_CheckAndLogRequiredLength(TAG, input, GUID_SIZE + 4))
+ {
+ error = ERROR_INVALID_DATA;
+ goto out;
+ }
+
+ CopyMemory(callback->presentation_id, Stream_Pointer(input), GUID_SIZE);
+ Stream_Seek(input, GUID_SIZE);
+ Stream_Read_UINT32(input, callback->stream_id);
+ DEBUG_TSMF("SET_CHANNEL_PARAMS StreamId=%" PRIu32 "", callback->stream_id);
+ ifman.output_pending = TRUE;
+ processed = TRUE;
+ break;
+
+ case EXCHANGE_CAPABILITIES_REQ:
+ error = tsmf_ifman_exchange_capability_request(&ifman);
+ processed = TRUE;
+ break;
+
+ case CHECK_FORMAT_SUPPORT_REQ:
+ error = tsmf_ifman_check_format_support_request(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_NEW_PRESENTATION:
+ error = tsmf_ifman_on_new_presentation(&ifman);
+ processed = TRUE;
+ break;
+
+ case ADD_STREAM:
+ error =
+ tsmf_ifman_add_stream(&ifman, ((TSMF_PLUGIN*)callback->plugin)->rdpcontext);
+ processed = TRUE;
+ break;
+
+ case SET_TOPOLOGY_REQ:
+ error = tsmf_ifman_set_topology_request(&ifman);
+ processed = TRUE;
+ break;
+
+ case REMOVE_STREAM:
+ error = tsmf_ifman_remove_stream(&ifman);
+ processed = TRUE;
+ break;
+
+ case SET_SOURCE_VIDEO_RECT:
+ error = tsmf_ifman_set_source_video_rect(&ifman);
+ processed = TRUE;
+ break;
+
+ case SHUTDOWN_PRESENTATION_REQ:
+ error = tsmf_ifman_shutdown_presentation(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_STREAM_VOLUME:
+ error = tsmf_ifman_on_stream_volume(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_CHANNEL_VOLUME:
+ error = tsmf_ifman_on_channel_volume(&ifman);
+ processed = TRUE;
+ break;
+
+ case SET_VIDEO_WINDOW:
+ error = tsmf_ifman_set_video_window(&ifman);
+ processed = TRUE;
+ break;
+
+ case UPDATE_GEOMETRY_INFO:
+ error = tsmf_ifman_update_geometry_info(&ifman);
+ processed = TRUE;
+ break;
+
+ case SET_ALLOCATOR:
+ error = tsmf_ifman_set_allocator(&ifman);
+ processed = TRUE;
+ break;
+
+ case NOTIFY_PREROLL:
+ error = tsmf_ifman_notify_preroll(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_SAMPLE:
+ error = tsmf_ifman_on_sample(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_FLUSH:
+ error = tsmf_ifman_on_flush(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_END_OF_STREAM:
+ error = tsmf_ifman_on_end_of_stream(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_PLAYBACK_STARTED:
+ error = tsmf_ifman_on_playback_started(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_PLAYBACK_PAUSED:
+ error = tsmf_ifman_on_playback_paused(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_PLAYBACK_RESTARTED:
+ error = tsmf_ifman_on_playback_restarted(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_PLAYBACK_STOPPED:
+ error = tsmf_ifman_on_playback_stopped(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_PLAYBACK_RATE_CHANGED:
+ error = tsmf_ifman_on_playback_rate_changed(&ifman);
+ processed = TRUE;
+ break;
+
+ case RIMCALL_RELEASE:
+ case RIMCALL_QUERYINTERFACE:
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ input = NULL;
+ ifman.input = NULL;
+
+ if (error)
+ {
+ WLog_ERR(TAG, "ifman data received processing error %" PRIu32 "", error);
+ }
+
+ if (!processed)
+ {
+ switch (FunctionId)
+ {
+ case RIMCALL_RELEASE:
+ /* [MS-RDPEXPS] 2.2.2.2 Interface Release (IFACE_RELEASE)
+ This message does not require a reply. */
+ processed = TRUE;
+ ifman.output_pending = 1;
+ break;
+
+ case RIMCALL_QUERYINTERFACE:
+ /* [MS-RDPEXPS] 2.2.2.1.2 Query Interface Response (QI_RSP)
+ This message is not supported in this channel. */
+ processed = TRUE;
+ break;
+ }
+
+ if (!processed)
+ {
+ WLog_ERR(TAG,
+ "Unknown InterfaceId: 0x%08" PRIX32 " MessageId: 0x%08" PRIX32
+ " FunctionId: 0x%08" PRIX32 "\n",
+ InterfaceId, MessageId, FunctionId);
+ /* When a request is not implemented we return empty response indicating error */
+ }
+
+ processed = TRUE;
+ }
+
+ if (processed && !ifman.output_pending)
+ {
+ /* Response packet does not have FunctionId */
+ length = Stream_GetPosition(output);
+ Stream_SetPosition(output, 0);
+ Stream_Write_UINT32(output, ifman.output_interface_id);
+ Stream_Write_UINT32(output, MessageId);
+ DEBUG_TSMF("response size %d", length);
+ error = callback->channel->Write(callback->channel, length, Stream_Buffer(output), NULL);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "response error %" PRIu32 "", error);
+ }
+ }
+
+out:
+ Stream_Free(output, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT tsmf_on_close(IWTSVirtualChannelCallback* pChannelCallback)
+{
+ TSMF_STREAM* stream = NULL;
+ TSMF_PRESENTATION* presentation = NULL;
+ TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback;
+ DEBUG_TSMF("");
+
+ if (callback->stream_id)
+ {
+ presentation = tsmf_presentation_find_by_id(callback->presentation_id);
+
+ if (presentation)
+ {
+ stream = tsmf_stream_find_by_id(presentation, callback->stream_id);
+
+ if (stream)
+ tsmf_stream_free(stream);
+ }
+ }
+
+ free(pChannelCallback);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT tsmf_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
+ IWTSVirtualChannel* pChannel, BYTE* Data, BOOL* pbAccept,
+ IWTSVirtualChannelCallback** ppCallback)
+{
+ TSMF_CHANNEL_CALLBACK* callback = NULL;
+ TSMF_LISTENER_CALLBACK* listener_callback = (TSMF_LISTENER_CALLBACK*)pListenerCallback;
+ DEBUG_TSMF("");
+ callback = (TSMF_CHANNEL_CALLBACK*)calloc(1, sizeof(TSMF_CHANNEL_CALLBACK));
+
+ if (!callback)
+ return CHANNEL_RC_NO_MEMORY;
+
+ callback->iface.OnDataReceived = tsmf_on_data_received;
+ callback->iface.OnClose = tsmf_on_close;
+ callback->iface.OnOpen = NULL;
+ callback->plugin = listener_callback->plugin;
+ callback->channel_mgr = listener_callback->channel_mgr;
+ callback->channel = pChannel;
+ *ppCallback = (IWTSVirtualChannelCallback*)callback;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT tsmf_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr)
+{
+ UINT status = 0;
+ TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*)pPlugin;
+ DEBUG_TSMF("");
+ tsmf->listener_callback = (TSMF_LISTENER_CALLBACK*)calloc(1, sizeof(TSMF_LISTENER_CALLBACK));
+
+ if (!tsmf->listener_callback)
+ return CHANNEL_RC_NO_MEMORY;
+
+ tsmf->listener_callback->iface.OnNewChannelConnection = tsmf_on_new_channel_connection;
+ tsmf->listener_callback->plugin = pPlugin;
+ tsmf->listener_callback->channel_mgr = pChannelMgr;
+ status = pChannelMgr->CreateListener(
+ pChannelMgr, "TSMF", 0, (IWTSListenerCallback*)tsmf->listener_callback, &(tsmf->listener));
+ tsmf->listener->pInterface = tsmf->iface.pInterface;
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT tsmf_plugin_terminated(IWTSPlugin* pPlugin)
+{
+ TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*)pPlugin;
+ DEBUG_TSMF("");
+ free(tsmf->listener_callback);
+ free(tsmf);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT tsmf_process_addin_args(IWTSPlugin* pPlugin, const ADDIN_ARGV* args)
+{
+ int status = 0;
+ DWORD flags = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*)pPlugin;
+ COMMAND_LINE_ARGUMENT_A tsmf_args[] = { { "sys", COMMAND_LINE_VALUE_REQUIRED, "<subsystem>",
+ NULL, NULL, -1, NULL, "audio subsystem" },
+ { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", NULL,
+ NULL, -1, NULL, "audio device name" },
+ { "decoder", COMMAND_LINE_VALUE_REQUIRED, "<subsystem>",
+ NULL, NULL, -1, NULL, "decoder subsystem" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
+ flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON;
+ status = CommandLineParseArgumentsA(args->argc, args->argv, tsmf_args, flags, tsmf, NULL, NULL);
+
+ if (status != 0)
+ return ERROR_INVALID_DATA;
+
+ arg = tsmf_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "sys")
+ {
+ tsmf->audio_name = _strdup(arg->Value);
+
+ if (!tsmf->audio_name)
+ return ERROR_OUTOFMEMORY;
+ }
+ CommandLineSwitchCase(arg, "dev")
+ {
+ tsmf->audio_device = _strdup(arg->Value);
+
+ if (!tsmf->audio_device)
+ return ERROR_OUTOFMEMORY;
+ }
+ CommandLineSwitchCase(arg, "decoder")
+ {
+ tsmf->decoder_name = _strdup(arg->Value);
+
+ if (!tsmf->decoder_name)
+ return ERROR_OUTOFMEMORY;
+ }
+ CommandLineSwitchDefault(arg)
+ {
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT tsmf_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
+{
+ UINT status = 0;
+ TSMF_PLUGIN* tsmf = NULL;
+ TsmfClientContext* context = NULL;
+ UINT error = CHANNEL_RC_NO_MEMORY;
+ tsmf = (TSMF_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, "tsmf");
+
+ if (!tsmf)
+ {
+ tsmf = (TSMF_PLUGIN*)calloc(1, sizeof(TSMF_PLUGIN));
+
+ if (!tsmf)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ tsmf->iface.Initialize = tsmf_plugin_initialize;
+ tsmf->iface.Connected = NULL;
+ tsmf->iface.Disconnected = NULL;
+ tsmf->iface.Terminated = tsmf_plugin_terminated;
+ tsmf->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints);
+ context = (TsmfClientContext*)calloc(1, sizeof(TsmfClientContext));
+
+ if (!context)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ goto error_context;
+ }
+
+ context->handle = (void*)tsmf;
+ tsmf->iface.pInterface = (void*)context;
+
+ if (!tsmf_media_init())
+ {
+ error = ERROR_INVALID_OPERATION;
+ goto error_init;
+ }
+
+ status = pEntryPoints->RegisterPlugin(pEntryPoints, "tsmf", &tsmf->iface);
+ }
+
+ if (status == CHANNEL_RC_OK)
+ {
+ status = tsmf_process_addin_args(&tsmf->iface, pEntryPoints->GetPluginData(pEntryPoints));
+ }
+
+ return status;
+error_init:
+ free(context);
+error_context:
+ free(tsmf);
+ return error;
+}
diff --git a/channels/tsmf/client/tsmf_main.h b/channels/tsmf/client/tsmf_main.h
new file mode 100644
index 0000000..fb783c1
--- /dev/null
+++ b/channels/tsmf/client/tsmf_main.h
@@ -0,0 +1,65 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_TSMF_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_TSMF_CLIENT_MAIN_H
+
+#include <freerdp/freerdp.h>
+
+typedef struct
+{
+ IWTSListenerCallback iface;
+
+ IWTSPlugin* plugin;
+ IWTSVirtualChannelManager* channel_mgr;
+} TSMF_LISTENER_CALLBACK;
+
+typedef struct
+{
+ IWTSVirtualChannelCallback iface;
+
+ IWTSPlugin* plugin;
+ IWTSVirtualChannelManager* channel_mgr;
+ IWTSVirtualChannel* channel;
+
+ BYTE presentation_id[GUID_SIZE];
+ UINT32 stream_id;
+} TSMF_CHANNEL_CALLBACK;
+
+typedef struct
+{
+ IWTSPlugin iface;
+
+ IWTSListener* listener;
+ TSMF_LISTENER_CALLBACK* listener_callback;
+
+ const char* decoder_name;
+ const char* audio_name;
+ const char* audio_device;
+
+ rdpContext* rdpcontext;
+} TSMF_PLUGIN;
+
+BOOL tsmf_send_eos_response(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id);
+BOOL tsmf_playback_ack(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id,
+ UINT64 duration, UINT32 data_size);
+
+#endif /* FREERDP_CHANNEL_TSMF_CLIENT_MAIN_H */
diff --git a/channels/tsmf/client/tsmf_media.c b/channels/tsmf/client/tsmf_media.c
new file mode 100644
index 0000000..5f47090
--- /dev/null
+++ b/channels/tsmf/client/tsmf_media.c
@@ -0,0 +1,1544 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - Media Container
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2012 Hewlett-Packard Development Company, L.P.
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#ifndef _WIN32
+#include <sys/time.h>
+#endif
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/string.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/client/tsmf.h>
+
+#include "tsmf_constants.h"
+#include "tsmf_types.h"
+#include "tsmf_decoder.h"
+#include "tsmf_audio.h"
+#include "tsmf_main.h"
+#include "tsmf_codec.h"
+#include "tsmf_media.h"
+
+#define AUDIO_TOLERANCE 10000000LL
+
+/* 1 second = 10,000,000 100ns units*/
+#define VIDEO_ADJUST_MAX 10 * 1000 * 1000
+
+#define MAX_ACK_TIME 666667
+
+#define AUDIO_MIN_BUFFER_LEVEL 3
+#define AUDIO_MAX_BUFFER_LEVEL 6
+
+#define VIDEO_MIN_BUFFER_LEVEL 10
+#define VIDEO_MAX_BUFFER_LEVEL 30
+
+struct S_TSMF_PRESENTATION
+{
+ BYTE presentation_id[GUID_SIZE];
+
+ const char* audio_name;
+ const char* audio_device;
+
+ IWTSVirtualChannelCallback* channel_callback;
+
+ UINT64 audio_start_time;
+ UINT64 audio_end_time;
+
+ UINT32 volume;
+ UINT32 muted;
+
+ wArrayList* stream_list;
+
+ int x;
+ int y;
+ int width;
+ int height;
+
+ int nr_rects;
+ void* rects;
+};
+
+struct S_TSMF_STREAM
+{
+ UINT32 stream_id;
+
+ TSMF_PRESENTATION* presentation;
+
+ ITSMFDecoder* decoder;
+
+ int major_type;
+ int eos;
+ UINT32 eos_message_id;
+ IWTSVirtualChannelCallback* eos_channel_callback;
+ int delayed_stop;
+ UINT32 width;
+ UINT32 height;
+
+ ITSMFAudioDevice* audio;
+ UINT32 sample_rate;
+ UINT32 channels;
+ UINT32 bits_per_sample;
+
+ /* The start time of last played sample */
+ UINT64 last_start_time;
+ /* The end_time of last played sample */
+ UINT64 last_end_time;
+ /* Next sample should not start before this system time. */
+ UINT64 next_start_time;
+
+ UINT32 minBufferLevel;
+ UINT32 maxBufferLevel;
+ UINT32 currentBufferLevel;
+
+ HANDLE play_thread;
+ HANDLE ack_thread;
+ HANDLE stopEvent;
+ HANDLE ready;
+
+ wQueue* sample_list;
+ wQueue* sample_ack_list;
+ rdpContext* rdpcontext;
+
+ BOOL seeking;
+};
+
+struct S_TSMF_SAMPLE
+{
+ UINT32 sample_id;
+ UINT64 start_time;
+ UINT64 end_time;
+ UINT64 duration;
+ UINT32 extensions;
+ UINT32 data_size;
+ BYTE* data;
+ UINT32 decoded_size;
+ UINT32 pixfmt;
+
+ BOOL invalidTimestamps;
+
+ TSMF_STREAM* stream;
+ IWTSVirtualChannelCallback* channel_callback;
+ UINT64 ack_time;
+};
+
+static wArrayList* presentation_list = NULL;
+static int TERMINATING = 0;
+
+static void _tsmf_presentation_free(void* obj);
+static void _tsmf_stream_free(void* obj);
+
+static UINT64 get_current_time(void)
+{
+ struct timeval tp;
+ gettimeofday(&tp, 0);
+ return ((UINT64)tp.tv_sec) * 10000000LL + ((UINT64)tp.tv_usec) * 10LL;
+}
+
+static TSMF_SAMPLE* tsmf_stream_pop_sample(TSMF_STREAM* stream, int sync)
+{
+ UINT32 count = 0;
+ TSMF_STREAM* s = NULL;
+ TSMF_SAMPLE* sample = NULL;
+ BOOL pending = FALSE;
+ TSMF_PRESENTATION* presentation = NULL;
+
+ if (!stream)
+ return NULL;
+
+ presentation = stream->presentation;
+
+ if (Queue_Count(stream->sample_list) < 1)
+ return NULL;
+
+ if (sync)
+ {
+ if (stream->decoder)
+ {
+ if (stream->decoder->GetDecodedData)
+ {
+ if (stream->major_type == TSMF_MAJOR_TYPE_AUDIO)
+ {
+ /* Check if some other stream has earlier sample that needs to be played first
+ */
+ /* Start time is more reliable than end time as some stream types seem to have
+ * incorrect end times from the server
+ */
+ if (stream->last_start_time > AUDIO_TOLERANCE)
+ {
+ ArrayList_Lock(presentation->stream_list);
+ count = ArrayList_Count(presentation->stream_list);
+
+ for (UINT32 index = 0; index < count; index++)
+ {
+ s = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index);
+
+ /* Start time is more reliable than end time as some stream types seem
+ * to have incorrect end times from the server
+ */
+ if (s != stream && !s->eos && s->last_start_time &&
+ s->last_start_time < stream->last_start_time - AUDIO_TOLERANCE)
+ {
+ DEBUG_TSMF("Pending due to audio tolerance");
+ pending = TRUE;
+ break;
+ }
+ }
+
+ ArrayList_Unlock(presentation->stream_list);
+ }
+ }
+ else
+ {
+ /* Start time is more reliable than end time as some stream types seem to have
+ * incorrect end times from the server
+ */
+ if (stream->last_start_time > presentation->audio_start_time)
+ {
+ DEBUG_TSMF("Pending due to stream start time > audio start time");
+ pending = TRUE;
+ }
+ }
+ }
+ }
+ }
+
+ if (pending)
+ return NULL;
+
+ sample = (TSMF_SAMPLE*)Queue_Dequeue(stream->sample_list);
+
+ /* Only update stream last end time if the sample end time is valid and greater than the current
+ * stream end time */
+ if (sample && (sample->end_time > stream->last_end_time) && (!sample->invalidTimestamps))
+ stream->last_end_time = sample->end_time;
+
+ /* Only update stream last start time if the sample start time is valid and greater than the
+ * current stream start time */
+ if (sample && (sample->start_time > stream->last_start_time) && (!sample->invalidTimestamps))
+ stream->last_start_time = sample->start_time;
+
+ return sample;
+}
+
+static void tsmf_sample_free(void* arg)
+{
+ TSMF_SAMPLE* sample = arg;
+
+ if (!sample)
+ return;
+
+ free(sample->data);
+ free(sample);
+}
+
+static BOOL tsmf_sample_ack(TSMF_SAMPLE* sample)
+{
+ if (!sample)
+ return FALSE;
+
+ return tsmf_playback_ack(sample->channel_callback, sample->sample_id, sample->duration,
+ sample->data_size);
+}
+
+static BOOL tsmf_sample_queue_ack(TSMF_SAMPLE* sample)
+{
+ if (!sample)
+ return FALSE;
+
+ if (!sample->stream)
+ return FALSE;
+
+ return Queue_Enqueue(sample->stream->sample_ack_list, sample);
+}
+
+/* Returns TRUE if no more samples are currently available
+ * Returns FALSE otherwise
+ */
+static BOOL tsmf_stream_process_ack(void* arg, BOOL force)
+{
+ TSMF_STREAM* stream = arg;
+ TSMF_SAMPLE* sample = NULL;
+ UINT64 ack_time = 0;
+ BOOL rc = FALSE;
+
+ if (!stream)
+ return TRUE;
+
+ Queue_Lock(stream->sample_ack_list);
+ sample = (TSMF_SAMPLE*)Queue_Peek(stream->sample_ack_list);
+
+ if (!sample)
+ {
+ rc = TRUE;
+ goto finally;
+ }
+
+ if (!force)
+ {
+ /* Do some min/max ack limiting if we have access to Buffer level information */
+ if (stream->decoder && stream->decoder->BufferLevel)
+ {
+ /* Try to keep buffer level below max by withholding acks */
+ if (stream->currentBufferLevel > stream->maxBufferLevel)
+ goto finally;
+ /* Try to keep buffer level above min by pushing acks through quickly */
+ else if (stream->currentBufferLevel < stream->minBufferLevel)
+ goto dequeue;
+ }
+
+ /* Time based acks only */
+ ack_time = get_current_time();
+
+ if (sample->ack_time > ack_time)
+ goto finally;
+ }
+
+dequeue:
+ sample = Queue_Dequeue(stream->sample_ack_list);
+
+ if (sample)
+ {
+ tsmf_sample_ack(sample);
+ tsmf_sample_free(sample);
+ }
+
+finally:
+ Queue_Unlock(stream->sample_ack_list);
+ return rc;
+}
+
+TSMF_PRESENTATION* tsmf_presentation_new(const BYTE* guid,
+ IWTSVirtualChannelCallback* pChannelCallback)
+{
+ wObject* obj = NULL;
+ TSMF_PRESENTATION* presentation = NULL;
+
+ if (!guid || !pChannelCallback)
+ return NULL;
+
+ presentation = (TSMF_PRESENTATION*)calloc(1, sizeof(TSMF_PRESENTATION));
+
+ if (!presentation)
+ {
+ WLog_ERR(TAG, "calloc failed");
+ return NULL;
+ }
+
+ CopyMemory(presentation->presentation_id, guid, GUID_SIZE);
+ presentation->channel_callback = pChannelCallback;
+ presentation->volume = 5000; /* 50% */
+ presentation->muted = 0;
+
+ if (!(presentation->stream_list = ArrayList_New(TRUE)))
+ goto error_stream_list;
+
+ obj = ArrayList_Object(presentation->stream_list);
+ if (!obj)
+ goto error_add;
+ obj->fnObjectFree = _tsmf_stream_free;
+
+ if (!ArrayList_Append(presentation_list, presentation))
+ goto error_add;
+
+ return presentation;
+error_add:
+ ArrayList_Free(presentation->stream_list);
+error_stream_list:
+ free(presentation);
+ return NULL;
+}
+
+static char* guid_to_string(const BYTE* guid, char* str, size_t len)
+{
+ if (!guid || !str)
+ return NULL;
+
+ for (size_t i = 0; i < GUID_SIZE && (len > 2 * i); i++)
+ sprintf_s(str + (2 * i), len - 2 * i, "%02" PRIX8 "", guid[i]);
+
+ return str;
+}
+
+TSMF_PRESENTATION* tsmf_presentation_find_by_id(const BYTE* guid)
+{
+ UINT32 count = 0;
+ BOOL found = FALSE;
+ char guid_str[GUID_SIZE * 2 + 1] = { 0 };
+ TSMF_PRESENTATION* presentation = NULL;
+ ArrayList_Lock(presentation_list);
+ count = ArrayList_Count(presentation_list);
+
+ for (size_t index = 0; index < count; index++)
+ {
+ presentation = (TSMF_PRESENTATION*)ArrayList_GetItem(presentation_list, index);
+
+ if (memcmp(presentation->presentation_id, guid, GUID_SIZE) == 0)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ ArrayList_Unlock(presentation_list);
+
+ if (!found)
+ WLog_WARN(TAG, "presentation id %s not found",
+ guid_to_string(guid, guid_str, sizeof(guid_str)));
+
+ return (found) ? presentation : NULL;
+}
+
+static BOOL tsmf_sample_playback_video(TSMF_SAMPLE* sample)
+{
+ UINT64 t = 0;
+ TSMF_VIDEO_FRAME_EVENT event;
+ TSMF_STREAM* stream = sample->stream;
+ TSMF_PRESENTATION* presentation = stream->presentation;
+ TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)sample->channel_callback;
+ TsmfClientContext* tsmf = (TsmfClientContext*)callback->plugin->pInterface;
+ DEBUG_TSMF("MessageId %" PRIu32 " EndTime %" PRIu64 " data_size %" PRIu32 " consumed.",
+ sample->sample_id, sample->end_time, sample->data_size);
+
+ if (sample->data)
+ {
+ t = get_current_time();
+
+ /* Start time is more reliable than end time as some stream types seem to have incorrect
+ * end times from the server
+ */
+ if (stream->next_start_time > t &&
+ ((sample->start_time >= presentation->audio_start_time) ||
+ ((sample->start_time < stream->last_start_time) && (!sample->invalidTimestamps))))
+ {
+ USleep((stream->next_start_time - t) / 10);
+ }
+
+ stream->next_start_time = t + sample->duration - 50000;
+ ZeroMemory(&event, sizeof(TSMF_VIDEO_FRAME_EVENT));
+ event.frameData = sample->data;
+ event.frameSize = sample->decoded_size;
+ event.framePixFmt = sample->pixfmt;
+ event.frameWidth = sample->stream->width;
+ event.frameHeight = sample->stream->height;
+ event.x = presentation->x;
+ event.y = presentation->y;
+ event.width = presentation->width;
+ event.height = presentation->height;
+
+ if (presentation->nr_rects > 0)
+ {
+ event.numVisibleRects = presentation->nr_rects;
+ event.visibleRects = (RECTANGLE_16*)calloc(event.numVisibleRects, sizeof(RECTANGLE_16));
+
+ if (!event.visibleRects)
+ {
+ WLog_ERR(TAG, "can't allocate memory for copy rectangles");
+ return FALSE;
+ }
+
+ memcpy(event.visibleRects, presentation->rects,
+ presentation->nr_rects * sizeof(RDP_RECT));
+ presentation->nr_rects = 0;
+ }
+
+#if 0
+ /* Dump a .ppm image for every 30 frames. Assuming the frame is in YUV format, we
+ extract the Y values to create a grayscale image. */
+ static int frame_id = 0;
+ char buf[100];
+
+ if ((frame_id % 30) == 0)
+ {
+ sprintf_s(buf, sizeof(buf), "/tmp/FreeRDP_Frame_%d.ppm", frame_id);
+ FILE* fp = fopen(buf, "wb");
+ if (fp)
+ {
+ fwrite("P5\n", 1, 3, fp);
+ sprintf_s(buf, sizeof(buf), "%"PRIu32" %"PRIu32"\n", sample->stream->width,
+ sample->stream->height);
+ fwrite(buf, 1, strnlen(buf, sizeof(buf)), fp);
+ fwrite("255\n", 1, 4, fp);
+ fwrite(sample->data, 1, sample->stream->width * sample->stream->height, fp);
+ fflush(fp);
+ fclose(fp);
+ }
+ }
+
+ frame_id++;
+#endif
+ /* The frame data ownership is passed to the event object, and is freed after the event is
+ * processed. */
+ sample->data = NULL;
+ sample->decoded_size = 0;
+
+ if (tsmf->FrameEvent)
+ tsmf->FrameEvent(tsmf, &event);
+
+ free(event.frameData);
+
+ if (event.visibleRects != NULL)
+ free(event.visibleRects);
+ }
+
+ return TRUE;
+}
+
+static BOOL tsmf_sample_playback_audio(TSMF_SAMPLE* sample)
+{
+ UINT64 latency = 0;
+ TSMF_STREAM* stream = sample->stream;
+ BOOL ret = 0;
+ DEBUG_TSMF("MessageId %" PRIu32 " EndTime %" PRIu64 " consumed.", sample->sample_id,
+ sample->end_time);
+
+ if (stream->audio && sample->data)
+ {
+ ret =
+ sample->stream->audio->Play(sample->stream->audio, sample->data, sample->decoded_size);
+ free(sample->data);
+ sample->data = NULL;
+ sample->decoded_size = 0;
+
+ if (stream->audio->GetLatency)
+ latency = stream->audio->GetLatency(stream->audio);
+ }
+ else
+ {
+ ret = TRUE;
+ latency = 0;
+ }
+
+ sample->ack_time = latency + get_current_time();
+
+ /* Only update stream times if the sample timestamps are valid */
+ if (!sample->invalidTimestamps)
+ {
+ stream->last_start_time = sample->start_time + latency;
+ stream->last_end_time = sample->end_time + latency;
+ stream->presentation->audio_start_time = sample->start_time + latency;
+ stream->presentation->audio_end_time = sample->end_time + latency;
+ }
+
+ return ret;
+}
+
+static BOOL tsmf_sample_playback(TSMF_SAMPLE* sample)
+{
+ BOOL ret = FALSE;
+ UINT32 width = 0;
+ UINT32 height = 0;
+ UINT32 pixfmt = 0;
+ TSMF_STREAM* stream = sample->stream;
+
+ if (stream->decoder)
+ {
+ if (stream->decoder->DecodeEx)
+ {
+ /* Try to "sync" video buffers to audio buffers by looking at the running time for each
+ * stream The difference between the two running times causes an offset between audio
+ * and video actual render times. So, we try to adjust timestamps on the video buffer to
+ * match those on the audio buffer.
+ */
+ if (stream->major_type == TSMF_MAJOR_TYPE_VIDEO)
+ {
+ TSMF_STREAM* temp_stream = NULL;
+ TSMF_PRESENTATION* presentation = stream->presentation;
+ ArrayList_Lock(presentation->stream_list);
+ int count = ArrayList_Count(presentation->stream_list);
+
+ for (size_t index = 0; index < count; index++)
+ {
+ UINT64 time_diff = 0;
+ temp_stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index);
+
+ if (temp_stream->major_type == TSMF_MAJOR_TYPE_AUDIO)
+ {
+ UINT64 video_time =
+ (UINT64)stream->decoder->GetRunningTime(stream->decoder);
+ UINT64 audio_time =
+ (UINT64)temp_stream->decoder->GetRunningTime(temp_stream->decoder);
+ UINT64 max_adjust = VIDEO_ADJUST_MAX;
+
+ if (video_time < audio_time)
+ max_adjust = -VIDEO_ADJUST_MAX;
+
+ if (video_time > audio_time)
+ time_diff = video_time - audio_time;
+ else
+ time_diff = audio_time - video_time;
+
+ time_diff = time_diff < VIDEO_ADJUST_MAX ? time_diff : max_adjust;
+ sample->start_time += time_diff;
+ sample->end_time += time_diff;
+ break;
+ }
+ }
+
+ ArrayList_Unlock(presentation->stream_list);
+ }
+
+ ret = stream->decoder->DecodeEx(stream->decoder, sample->data, sample->data_size,
+ sample->extensions, sample->start_time,
+ sample->end_time, sample->duration);
+ }
+ else
+ {
+ ret = stream->decoder->Decode(stream->decoder, sample->data, sample->data_size,
+ sample->extensions);
+ }
+ }
+
+ if (!ret)
+ {
+ WLog_ERR(TAG, "decode error, queue ack anyways");
+
+ if (!tsmf_sample_queue_ack(sample))
+ {
+ WLog_ERR(TAG, "error queuing sample for ack");
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ free(sample->data);
+ sample->data = NULL;
+
+ if (stream->major_type == TSMF_MAJOR_TYPE_VIDEO)
+ {
+ if (stream->decoder->GetDecodedFormat)
+ {
+ pixfmt = stream->decoder->GetDecodedFormat(stream->decoder);
+
+ if (pixfmt == ((UINT32)-1))
+ {
+ WLog_ERR(TAG, "unable to decode video format");
+
+ if (!tsmf_sample_queue_ack(sample))
+ {
+ WLog_ERR(TAG, "error queuing sample for ack");
+ }
+
+ return FALSE;
+ }
+
+ sample->pixfmt = pixfmt;
+ }
+
+ if (stream->decoder->GetDecodedDimension)
+ {
+ ret = stream->decoder->GetDecodedDimension(stream->decoder, &width, &height);
+
+ if (ret && (width != stream->width || height != stream->height))
+ {
+ DEBUG_TSMF("video dimension changed to %" PRIu32 " x %" PRIu32 "", width, height);
+ stream->width = width;
+ stream->height = height;
+ }
+ }
+ }
+
+ if (stream->decoder->GetDecodedData)
+ {
+ sample->data = stream->decoder->GetDecodedData(stream->decoder, &sample->decoded_size);
+
+ switch (sample->stream->major_type)
+ {
+ case TSMF_MAJOR_TYPE_VIDEO:
+ ret = tsmf_sample_playback_video(sample) && tsmf_sample_queue_ack(sample);
+ break;
+
+ case TSMF_MAJOR_TYPE_AUDIO:
+ ret = tsmf_sample_playback_audio(sample) && tsmf_sample_queue_ack(sample);
+ break;
+ }
+ }
+ else
+ {
+ UINT64 ack_anticipation_time = get_current_time();
+ BOOL buffer_filled = TRUE;
+
+ /* Classify the buffer as filled once it reaches minimum level */
+ if (stream->decoder->BufferLevel)
+ {
+ if (stream->currentBufferLevel < stream->minBufferLevel)
+ buffer_filled = FALSE;
+ }
+
+ ack_anticipation_time +=
+ (sample->duration / 2 < MAX_ACK_TIME) ? sample->duration / 2 : MAX_ACK_TIME;
+
+ switch (sample->stream->major_type)
+ {
+ case TSMF_MAJOR_TYPE_VIDEO:
+ {
+ break;
+ }
+
+ case TSMF_MAJOR_TYPE_AUDIO:
+ {
+ break;
+ }
+ }
+
+ sample->ack_time = ack_anticipation_time;
+
+ if (!tsmf_sample_queue_ack(sample))
+ {
+ WLog_ERR(TAG, "error queuing sample for ack");
+ ret = FALSE;
+ }
+ }
+
+ return ret;
+}
+
+static DWORD WINAPI tsmf_stream_ack_func(LPVOID arg)
+{
+ HANDLE hdl[2];
+ TSMF_STREAM* stream = (TSMF_STREAM*)arg;
+ UINT error = CHANNEL_RC_OK;
+ DEBUG_TSMF("in %" PRIu32 "", stream->stream_id);
+ hdl[0] = stream->stopEvent;
+ hdl[1] = Queue_Event(stream->sample_ack_list);
+
+ while (1)
+ {
+ DWORD ev = WaitForMultipleObjects(2, hdl, FALSE, 1000);
+
+ if (ev == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error);
+ break;
+ }
+
+ if (stream->decoder)
+ if (stream->decoder->BufferLevel)
+ stream->currentBufferLevel = stream->decoder->BufferLevel(stream->decoder);
+
+ if (stream->eos)
+ {
+ while ((stream->currentBufferLevel > 0) && !(tsmf_stream_process_ack(stream, TRUE)))
+ {
+ DEBUG_TSMF("END OF STREAM PROCESSING!");
+
+ if (stream->decoder && stream->decoder->BufferLevel)
+ stream->currentBufferLevel = stream->decoder->BufferLevel(stream->decoder);
+ else
+ stream->currentBufferLevel = 1;
+
+ USleep(1000);
+ }
+
+ tsmf_send_eos_response(stream->eos_channel_callback, stream->eos_message_id);
+ stream->eos = 0;
+
+ if (stream->delayed_stop)
+ {
+ DEBUG_TSMF("Finishing delayed stream stop, now that eos has processed.");
+ tsmf_stream_flush(stream);
+
+ if (stream->decoder && stream->decoder->Control)
+ stream->decoder->Control(stream->decoder, Control_Stop, NULL);
+ }
+ }
+
+ /* Stream stopped force all of the acks to happen */
+ if (ev == WAIT_OBJECT_0)
+ {
+ DEBUG_TSMF("ack: Stream stopped!");
+
+ while (1)
+ {
+ if (tsmf_stream_process_ack(stream, TRUE))
+ break;
+
+ USleep(1000);
+ }
+
+ break;
+ }
+
+ if (tsmf_stream_process_ack(stream, FALSE))
+ continue;
+
+ if (stream->currentBufferLevel > stream->minBufferLevel)
+ USleep(1000);
+ }
+
+ if (error && stream->rdpcontext)
+ setChannelError(stream->rdpcontext, error, "tsmf_stream_ack_func reported an error");
+
+ DEBUG_TSMF("out %" PRIu32 "", stream->stream_id);
+ ExitThread(error);
+ return error;
+}
+
+static DWORD WINAPI tsmf_stream_playback_func(LPVOID arg)
+{
+ HANDLE hdl[2];
+ TSMF_SAMPLE* sample = NULL;
+ TSMF_STREAM* stream = (TSMF_STREAM*)arg;
+ TSMF_PRESENTATION* presentation = stream->presentation;
+ UINT error = CHANNEL_RC_OK;
+ DWORD status = 0;
+ DEBUG_TSMF("in %" PRIu32 "", stream->stream_id);
+
+ if (stream->major_type == TSMF_MAJOR_TYPE_AUDIO && stream->sample_rate && stream->channels &&
+ stream->bits_per_sample)
+ {
+ if (stream->decoder)
+ {
+ if (stream->decoder->GetDecodedData)
+ {
+ stream->audio = tsmf_load_audio_device(
+ presentation->audio_name && presentation->audio_name[0]
+ ? presentation->audio_name
+ : NULL,
+ presentation->audio_device && presentation->audio_device[0]
+ ? presentation->audio_device
+ : NULL);
+
+ if (stream->audio)
+ {
+ stream->audio->SetFormat(stream->audio, stream->sample_rate, stream->channels,
+ stream->bits_per_sample);
+ }
+ }
+ }
+ }
+
+ hdl[0] = stream->stopEvent;
+ hdl[1] = Queue_Event(stream->sample_list);
+
+ while (1)
+ {
+ status = WaitForMultipleObjects(2, hdl, FALSE, 1000);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error);
+ break;
+ }
+
+ status = WaitForSingleObject(stream->stopEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
+ break;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ if (stream->decoder)
+ if (stream->decoder->BufferLevel)
+ stream->currentBufferLevel = stream->decoder->BufferLevel(stream->decoder);
+
+ sample = tsmf_stream_pop_sample(stream, 0);
+
+ if (sample && !tsmf_sample_playback(sample))
+ {
+ WLog_ERR(TAG, "error playing sample");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (stream->currentBufferLevel > stream->minBufferLevel)
+ USleep(1000);
+ }
+
+ if (stream->audio)
+ {
+ stream->audio->Free(stream->audio);
+ stream->audio = NULL;
+ }
+
+ if (error && stream->rdpcontext)
+ setChannelError(stream->rdpcontext, error, "tsmf_stream_playback_func reported an error");
+
+ DEBUG_TSMF("out %" PRIu32 "", stream->stream_id);
+ ExitThread(error);
+ return error;
+}
+
+static BOOL tsmf_stream_start(TSMF_STREAM* stream)
+{
+ if (!stream || !stream->presentation || !stream->decoder || !stream->decoder->Control)
+ return TRUE;
+
+ stream->eos = 0;
+ return stream->decoder->Control(stream->decoder, Control_Restart, NULL);
+}
+
+static BOOL tsmf_stream_stop(TSMF_STREAM* stream)
+{
+ if (!stream || !stream->decoder || !stream->decoder->Control)
+ return TRUE;
+
+ /* If stopping after eos - we delay until the eos has been processed
+ * this allows us to process any buffers that have been acked even though
+ * they have not actually been completely processes by the decoder
+ */
+ if (stream->eos)
+ {
+ DEBUG_TSMF("Setting up a delayed stop for once the eos has been processed.");
+ stream->delayed_stop = 1;
+ return TRUE;
+ }
+ /* Otherwise force stop immediately */
+ else
+ {
+ DEBUG_TSMF("Stop with no pending eos response, so do it immediately.");
+ tsmf_stream_flush(stream);
+ return stream->decoder->Control(stream->decoder, Control_Stop, NULL);
+ }
+}
+
+static BOOL tsmf_stream_pause(TSMF_STREAM* stream)
+{
+ if (!stream || !stream->decoder || !stream->decoder->Control)
+ return TRUE;
+
+ return stream->decoder->Control(stream->decoder, Control_Pause, NULL);
+}
+
+static BOOL tsmf_stream_restart(TSMF_STREAM* stream)
+{
+ if (!stream || !stream->decoder || !stream->decoder->Control)
+ return TRUE;
+
+ stream->eos = 0;
+ return stream->decoder->Control(stream->decoder, Control_Restart, NULL);
+}
+
+static BOOL tsmf_stream_change_volume(TSMF_STREAM* stream, UINT32 newVolume, UINT32 muted)
+{
+ if (!stream || !stream->decoder)
+ return TRUE;
+
+ if (stream->decoder != NULL && stream->decoder->ChangeVolume)
+ {
+ return stream->decoder->ChangeVolume(stream->decoder, newVolume, muted);
+ }
+ else if (stream->audio != NULL && stream->audio->ChangeVolume)
+ {
+ return stream->audio->ChangeVolume(stream->audio, newVolume, muted);
+ }
+
+ return TRUE;
+}
+
+BOOL tsmf_presentation_volume_changed(TSMF_PRESENTATION* presentation, UINT32 newVolume,
+ UINT32 muted)
+{
+ TSMF_STREAM* stream = NULL;
+ BOOL ret = TRUE;
+ presentation->volume = newVolume;
+ presentation->muted = muted;
+ ArrayList_Lock(presentation->stream_list);
+ size_t count = ArrayList_Count(presentation->stream_list);
+
+ for (size_t index = 0; index < count; index++)
+ {
+ stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index);
+ ret &= tsmf_stream_change_volume(stream, newVolume, muted);
+ }
+
+ ArrayList_Unlock(presentation->stream_list);
+ return ret;
+}
+
+BOOL tsmf_presentation_paused(TSMF_PRESENTATION* presentation)
+{
+ TSMF_STREAM* stream = NULL;
+ BOOL ret = TRUE;
+ ArrayList_Lock(presentation->stream_list);
+ size_t count = ArrayList_Count(presentation->stream_list);
+
+ for (size_t index = 0; index < count; index++)
+ {
+ stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index);
+ ret &= tsmf_stream_pause(stream);
+ }
+
+ ArrayList_Unlock(presentation->stream_list);
+ return ret;
+}
+
+BOOL tsmf_presentation_restarted(TSMF_PRESENTATION* presentation)
+{
+ TSMF_STREAM* stream = NULL;
+ BOOL ret = TRUE;
+ ArrayList_Lock(presentation->stream_list);
+ size_t count = ArrayList_Count(presentation->stream_list);
+
+ for (size_t index = 0; index < count; index++)
+ {
+ stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index);
+ ret &= tsmf_stream_restart(stream);
+ }
+
+ ArrayList_Unlock(presentation->stream_list);
+ return ret;
+}
+
+BOOL tsmf_presentation_start(TSMF_PRESENTATION* presentation)
+{
+ TSMF_STREAM* stream = NULL;
+ BOOL ret = TRUE;
+ ArrayList_Lock(presentation->stream_list);
+ size_t count = ArrayList_Count(presentation->stream_list);
+
+ for (size_t index = 0; index < count; index++)
+ {
+ stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index);
+ ret &= tsmf_stream_start(stream);
+ }
+
+ ArrayList_Unlock(presentation->stream_list);
+ return ret;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT tsmf_presentation_sync(TSMF_PRESENTATION* presentation)
+{
+ UINT error = 0;
+ ArrayList_Lock(presentation->stream_list);
+ size_t count = ArrayList_Count(presentation->stream_list);
+
+ for (size_t index = 0; index < count; index++)
+ {
+ TSMF_STREAM* stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index);
+
+ if (WaitForSingleObject(stream->ready, 500) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
+ return error;
+ }
+ }
+
+ ArrayList_Unlock(presentation->stream_list);
+ return CHANNEL_RC_OK;
+}
+
+BOOL tsmf_presentation_stop(TSMF_PRESENTATION* presentation)
+{
+ TSMF_STREAM* stream = NULL;
+ BOOL ret = TRUE;
+ ArrayList_Lock(presentation->stream_list);
+ size_t count = ArrayList_Count(presentation->stream_list);
+
+ for (size_t index = 0; index < count; index++)
+ {
+ stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index);
+ ret &= tsmf_stream_stop(stream);
+ }
+
+ ArrayList_Unlock(presentation->stream_list);
+ presentation->audio_start_time = 0;
+ presentation->audio_end_time = 0;
+ return ret;
+}
+
+BOOL tsmf_presentation_set_geometry_info(TSMF_PRESENTATION* presentation, UINT32 x, UINT32 y,
+ UINT32 width, UINT32 height, int num_rects,
+ RDP_RECT* rects)
+{
+ TSMF_STREAM* stream = NULL;
+ void* tmp_rects = NULL;
+ BOOL ret = TRUE;
+
+ /* The server may send messages with invalid width / height.
+ * Ignore those messages. */
+ if (!width || !height)
+ return TRUE;
+
+ /* Streams can be added/removed from the presentation and the server will resend geometry info
+ * when a new stream is added to the presentation. Also, num_rects is used to indicate whether
+ * or not the window is visible. So, always process a valid message with unchanged position/size
+ * and/or no visibility rects.
+ */
+ presentation->x = x;
+ presentation->y = y;
+ presentation->width = width;
+ presentation->height = height;
+ tmp_rects = realloc(presentation->rects, sizeof(RDP_RECT) * num_rects);
+
+ if (!tmp_rects && num_rects)
+ return FALSE;
+
+ presentation->nr_rects = num_rects;
+ presentation->rects = tmp_rects;
+ if (presentation->rects)
+ CopyMemory(presentation->rects, rects, sizeof(RDP_RECT) * num_rects);
+ ArrayList_Lock(presentation->stream_list);
+ size_t count = ArrayList_Count(presentation->stream_list);
+
+ for (size_t index = 0; index < count; index++)
+ {
+ stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index);
+
+ if (!stream->decoder)
+ continue;
+
+ if (stream->decoder->UpdateRenderingArea)
+ {
+ ret = stream->decoder->UpdateRenderingArea(stream->decoder, x, y, width, height,
+ num_rects, rects);
+ }
+ }
+
+ ArrayList_Unlock(presentation->stream_list);
+ return ret;
+}
+
+void tsmf_presentation_set_audio_device(TSMF_PRESENTATION* presentation, const char* name,
+ const char* device)
+{
+ presentation->audio_name = name;
+ presentation->audio_device = device;
+}
+
+BOOL tsmf_stream_flush(TSMF_STREAM* stream)
+{
+ BOOL ret = TRUE;
+
+ // TSMF_SAMPLE* sample;
+ /* TODO: free lists */
+ if (stream->audio)
+ ret = stream->audio->Flush(stream->audio);
+
+ stream->eos = 0;
+ stream->eos_message_id = 0;
+ stream->eos_channel_callback = NULL;
+ stream->delayed_stop = 0;
+ stream->last_end_time = 0;
+ stream->next_start_time = 0;
+
+ if (stream->major_type == TSMF_MAJOR_TYPE_AUDIO)
+ {
+ stream->presentation->audio_start_time = 0;
+ stream->presentation->audio_end_time = 0;
+ }
+
+ return TRUE;
+}
+
+void _tsmf_presentation_free(void* obj)
+{
+ TSMF_PRESENTATION* presentation = (TSMF_PRESENTATION*)obj;
+
+ if (presentation)
+ {
+ tsmf_presentation_stop(presentation);
+ ArrayList_Clear(presentation->stream_list);
+ ArrayList_Free(presentation->stream_list);
+ free(presentation->rects);
+ ZeroMemory(presentation, sizeof(TSMF_PRESENTATION));
+ free(presentation);
+ }
+}
+
+void tsmf_presentation_free(TSMF_PRESENTATION* presentation)
+{
+ ArrayList_Remove(presentation_list, presentation);
+}
+
+TSMF_STREAM* tsmf_stream_new(TSMF_PRESENTATION* presentation, UINT32 stream_id,
+ rdpContext* rdpcontext)
+{
+ wObject* obj = NULL;
+ TSMF_STREAM* stream = NULL;
+ stream = tsmf_stream_find_by_id(presentation, stream_id);
+
+ if (stream)
+ {
+ WLog_ERR(TAG, "duplicated stream id %" PRIu32 "!", stream_id);
+ return NULL;
+ }
+
+ stream = (TSMF_STREAM*)calloc(1, sizeof(TSMF_STREAM));
+
+ if (!stream)
+ {
+ WLog_ERR(TAG, "Calloc failed");
+ return NULL;
+ }
+
+ stream->minBufferLevel = VIDEO_MIN_BUFFER_LEVEL;
+ stream->maxBufferLevel = VIDEO_MAX_BUFFER_LEVEL;
+ stream->currentBufferLevel = 1;
+ stream->seeking = FALSE;
+ stream->eos = 0;
+ stream->eos_message_id = 0;
+ stream->eos_channel_callback = NULL;
+ stream->stream_id = stream_id;
+ stream->presentation = presentation;
+ stream->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (!stream->stopEvent)
+ goto error_stopEvent;
+
+ stream->ready = CreateEvent(NULL, TRUE, TRUE, NULL);
+
+ if (!stream->ready)
+ goto error_ready;
+
+ stream->sample_list = Queue_New(TRUE, -1, -1);
+
+ if (!stream->sample_list)
+ goto error_sample_list;
+
+ obj = Queue_Object(stream->sample_list);
+ if (!obj)
+ goto error_sample_ack_list;
+ obj->fnObjectFree = tsmf_sample_free;
+
+ stream->sample_ack_list = Queue_New(TRUE, -1, -1);
+
+ if (!stream->sample_ack_list)
+ goto error_sample_ack_list;
+
+ obj = Queue_Object(stream->sample_ack_list);
+ if (!obj)
+ goto error_play_thread;
+ obj->fnObjectFree = tsmf_sample_free;
+
+ stream->play_thread =
+ CreateThread(NULL, 0, tsmf_stream_playback_func, stream, CREATE_SUSPENDED, NULL);
+
+ if (!stream->play_thread)
+ goto error_play_thread;
+
+ stream->ack_thread =
+ CreateThread(NULL, 0, tsmf_stream_ack_func, stream, CREATE_SUSPENDED, NULL);
+
+ if (!stream->ack_thread)
+ goto error_ack_thread;
+
+ if (!ArrayList_Append(presentation->stream_list, stream))
+ goto error_add;
+
+ stream->rdpcontext = rdpcontext;
+ return stream;
+error_add:
+ SetEvent(stream->stopEvent);
+
+ if (WaitForSingleObject(stream->ack_thread, INFINITE) == WAIT_FAILED)
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", GetLastError());
+
+error_ack_thread:
+ SetEvent(stream->stopEvent);
+
+ if (WaitForSingleObject(stream->play_thread, INFINITE) == WAIT_FAILED)
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", GetLastError());
+
+error_play_thread:
+ Queue_Free(stream->sample_ack_list);
+error_sample_ack_list:
+ Queue_Free(stream->sample_list);
+error_sample_list:
+ CloseHandle(stream->ready);
+error_ready:
+ CloseHandle(stream->stopEvent);
+error_stopEvent:
+ free(stream);
+ return NULL;
+}
+
+void tsmf_stream_start_threads(TSMF_STREAM* stream)
+{
+ ResumeThread(stream->play_thread);
+ ResumeThread(stream->ack_thread);
+}
+
+TSMF_STREAM* tsmf_stream_find_by_id(TSMF_PRESENTATION* presentation, UINT32 stream_id)
+{
+ BOOL found = FALSE;
+ TSMF_STREAM* stream = NULL;
+ ArrayList_Lock(presentation->stream_list);
+ size_t count = ArrayList_Count(presentation->stream_list);
+
+ for (size_t index = 0; index < count; index++)
+ {
+ stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index);
+
+ if (stream->stream_id == stream_id)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ ArrayList_Unlock(presentation->stream_list);
+ return (found) ? stream : NULL;
+}
+
+static void tsmf_stream_resync(void* arg)
+{
+ TSMF_STREAM* stream = arg;
+ ResetEvent(stream->ready);
+}
+
+BOOL tsmf_stream_set_format(TSMF_STREAM* stream, const char* name, wStream* s)
+{
+ TS_AM_MEDIA_TYPE mediatype;
+ BOOL ret = TRUE;
+
+ if (stream->decoder)
+ {
+ WLog_ERR(TAG, "duplicated call");
+ return FALSE;
+ }
+
+ if (!tsmf_codec_parse_media_type(&mediatype, s))
+ {
+ WLog_ERR(TAG, "unable to parse media type");
+ return FALSE;
+ }
+
+ if (mediatype.MajorType == TSMF_MAJOR_TYPE_VIDEO)
+ {
+ DEBUG_TSMF("video width %" PRIu32 " height %" PRIu32 " bit_rate %" PRIu32
+ " frame_rate %f codec_data %" PRIu32 "",
+ mediatype.Width, mediatype.Height, mediatype.BitRate,
+ (double)mediatype.SamplesPerSecond.Numerator /
+ (double)mediatype.SamplesPerSecond.Denominator,
+ mediatype.ExtraDataSize);
+ stream->minBufferLevel = VIDEO_MIN_BUFFER_LEVEL;
+ stream->maxBufferLevel = VIDEO_MAX_BUFFER_LEVEL;
+ }
+ else if (mediatype.MajorType == TSMF_MAJOR_TYPE_AUDIO)
+ {
+ DEBUG_TSMF("audio channel %" PRIu32 " sample_rate %" PRIu32 " bits_per_sample %" PRIu32
+ " codec_data %" PRIu32 "",
+ mediatype.Channels, mediatype.SamplesPerSecond.Numerator,
+ mediatype.BitsPerSample, mediatype.ExtraDataSize);
+ stream->sample_rate = mediatype.SamplesPerSecond.Numerator;
+ stream->channels = mediatype.Channels;
+ stream->bits_per_sample = mediatype.BitsPerSample;
+
+ if (stream->bits_per_sample == 0)
+ stream->bits_per_sample = 16;
+
+ stream->minBufferLevel = AUDIO_MIN_BUFFER_LEVEL;
+ stream->maxBufferLevel = AUDIO_MAX_BUFFER_LEVEL;
+ }
+
+ stream->major_type = mediatype.MajorType;
+ stream->width = mediatype.Width;
+ stream->height = mediatype.Height;
+ stream->decoder = tsmf_load_decoder(name, &mediatype);
+ ret &= tsmf_stream_change_volume(stream, stream->presentation->volume,
+ stream->presentation->muted);
+
+ if (!stream->decoder)
+ return FALSE;
+
+ if (stream->decoder->SetAckFunc)
+ ret &= stream->decoder->SetAckFunc(stream->decoder, tsmf_stream_process_ack, stream);
+
+ if (stream->decoder->SetSyncFunc)
+ ret &= stream->decoder->SetSyncFunc(stream->decoder, tsmf_stream_resync, stream);
+
+ return ret;
+}
+
+void tsmf_stream_end(TSMF_STREAM* stream, UINT32 message_id,
+ IWTSVirtualChannelCallback* pChannelCallback)
+{
+ if (!stream)
+ return;
+
+ stream->eos = 1;
+ stream->eos_message_id = message_id;
+ stream->eos_channel_callback = pChannelCallback;
+}
+
+void _tsmf_stream_free(void* obj)
+{
+ TSMF_STREAM* stream = (TSMF_STREAM*)obj;
+
+ if (!stream)
+ return;
+
+ tsmf_stream_stop(stream);
+ SetEvent(stream->stopEvent);
+
+ if (stream->play_thread)
+ {
+ if (WaitForSingleObject(stream->play_thread, INFINITE) == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", GetLastError());
+ return;
+ }
+
+ CloseHandle(stream->play_thread);
+ stream->play_thread = NULL;
+ }
+
+ if (stream->ack_thread)
+ {
+ if (WaitForSingleObject(stream->ack_thread, INFINITE) == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", GetLastError());
+ return;
+ }
+
+ CloseHandle(stream->ack_thread);
+ stream->ack_thread = NULL;
+ }
+
+ Queue_Free(stream->sample_list);
+ Queue_Free(stream->sample_ack_list);
+
+ if (stream->decoder && stream->decoder->Free)
+ {
+ stream->decoder->Free(stream->decoder);
+ stream->decoder = NULL;
+ }
+
+ CloseHandle(stream->stopEvent);
+ CloseHandle(stream->ready);
+ ZeroMemory(stream, sizeof(TSMF_STREAM));
+ free(stream);
+}
+
+void tsmf_stream_free(TSMF_STREAM* stream)
+{
+ TSMF_PRESENTATION* presentation = stream->presentation;
+ ArrayList_Remove(presentation->stream_list, stream);
+}
+
+BOOL tsmf_stream_push_sample(TSMF_STREAM* stream, IWTSVirtualChannelCallback* pChannelCallback,
+ UINT32 sample_id, UINT64 start_time, UINT64 end_time, UINT64 duration,
+ UINT32 extensions, UINT32 data_size, BYTE* data)
+{
+ TSMF_SAMPLE* sample = NULL;
+ SetEvent(stream->ready);
+
+ if (TERMINATING)
+ return TRUE;
+
+ sample = (TSMF_SAMPLE*)calloc(1, sizeof(TSMF_SAMPLE));
+
+ if (!sample)
+ {
+ WLog_ERR(TAG, "calloc sample failed!");
+ return FALSE;
+ }
+
+ sample->sample_id = sample_id;
+ sample->start_time = start_time;
+ sample->end_time = end_time;
+ sample->duration = duration;
+ sample->extensions = extensions;
+
+ if ((sample->extensions & 0x00000080) || (sample->extensions & 0x00000040))
+ sample->invalidTimestamps = TRUE;
+ else
+ sample->invalidTimestamps = FALSE;
+
+ sample->stream = stream;
+ sample->channel_callback = pChannelCallback;
+ sample->data_size = data_size;
+ sample->data = calloc(1, data_size + TSMF_BUFFER_PADDING_SIZE);
+
+ if (!sample->data)
+ {
+ WLog_ERR(TAG, "calloc sample->data failed!");
+ free(sample);
+ return FALSE;
+ }
+
+ CopyMemory(sample->data, data, data_size);
+ return Queue_Enqueue(stream->sample_list, sample);
+}
+
+#ifndef _WIN32
+
+static void tsmf_signal_handler(int s)
+{
+ TERMINATING = 1;
+ ArrayList_Free(presentation_list);
+
+ if (s == SIGINT)
+ {
+ signal(s, SIG_DFL);
+ kill(getpid(), s);
+ }
+ else if (s == SIGUSR1)
+ {
+ signal(s, SIG_DFL);
+ }
+}
+
+#endif
+
+BOOL tsmf_media_init(void)
+{
+ wObject* obj = NULL;
+#ifndef _WIN32
+ struct sigaction sigtrap;
+ sigtrap.sa_handler = tsmf_signal_handler;
+ sigemptyset(&sigtrap.sa_mask);
+ sigtrap.sa_flags = 0;
+ sigaction(SIGINT, &sigtrap, 0);
+ sigaction(SIGUSR1, &sigtrap, 0);
+#endif
+
+ if (!presentation_list)
+ {
+ presentation_list = ArrayList_New(TRUE);
+
+ if (!presentation_list)
+ return FALSE;
+
+ obj = ArrayList_Object(presentation_list);
+ if (!obj)
+ return FALSE;
+ obj->fnObjectFree = _tsmf_presentation_free;
+ }
+
+ return TRUE;
+}
diff --git a/channels/tsmf/client/tsmf_media.h b/channels/tsmf/client/tsmf_media.h
new file mode 100644
index 0000000..b98e322
--- /dev/null
+++ b/channels/tsmf/client/tsmf_media.h
@@ -0,0 +1,72 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - Media Container
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2012 Hewlett-Packard Development Company, L.P.
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The media container maintains a global list of presentations, and a list of
+ * streams in each presentation.
+ */
+
+#ifndef FREERDP_CHANNEL_TSMF_CLIENT_MEDIA_H
+#define FREERDP_CHANNEL_TSMF_CLIENT_MEDIA_H
+
+#include <freerdp/freerdp.h>
+
+typedef struct S_TSMF_PRESENTATION TSMF_PRESENTATION;
+
+typedef struct S_TSMF_STREAM TSMF_STREAM;
+
+typedef struct S_TSMF_SAMPLE TSMF_SAMPLE;
+
+TSMF_PRESENTATION* tsmf_presentation_new(const BYTE* guid,
+ IWTSVirtualChannelCallback* pChannelCallback);
+TSMF_PRESENTATION* tsmf_presentation_find_by_id(const BYTE* guid);
+BOOL tsmf_presentation_start(TSMF_PRESENTATION* presentation);
+BOOL tsmf_presentation_stop(TSMF_PRESENTATION* presentation);
+UINT tsmf_presentation_sync(TSMF_PRESENTATION* presentation);
+BOOL tsmf_presentation_paused(TSMF_PRESENTATION* presentation);
+BOOL tsmf_presentation_restarted(TSMF_PRESENTATION* presentation);
+BOOL tsmf_presentation_volume_changed(TSMF_PRESENTATION* presentation, UINT32 newVolume,
+ UINT32 muted);
+BOOL tsmf_presentation_set_geometry_info(TSMF_PRESENTATION* presentation, UINT32 x, UINT32 y,
+ UINT32 width, UINT32 height, int num_rects,
+ RDP_RECT* rects);
+void tsmf_presentation_set_audio_device(TSMF_PRESENTATION* presentation, const char* name,
+ const char* device);
+void tsmf_presentation_free(TSMF_PRESENTATION* presentation);
+
+TSMF_STREAM* tsmf_stream_new(TSMF_PRESENTATION* presentation, UINT32 stream_id,
+ rdpContext* rdpcontext);
+TSMF_STREAM* tsmf_stream_find_by_id(TSMF_PRESENTATION* presentation, UINT32 stream_id);
+BOOL tsmf_stream_set_format(TSMF_STREAM* stream, const char* name, wStream* s);
+void tsmf_stream_end(TSMF_STREAM* stream, UINT32 message_id,
+ IWTSVirtualChannelCallback* pChannelCallback);
+void tsmf_stream_free(TSMF_STREAM* stream);
+BOOL tsmf_stream_flush(TSMF_STREAM* stream);
+
+BOOL tsmf_stream_push_sample(TSMF_STREAM* stream, IWTSVirtualChannelCallback* pChannelCallback,
+ UINT32 sample_id, UINT64 start_time, UINT64 end_time, UINT64 duration,
+ UINT32 extensions, UINT32 data_size, BYTE* data);
+
+BOOL tsmf_media_init(void);
+void tsmf_stream_start_threads(TSMF_STREAM* stream);
+
+#endif /* FREERDP_CHANNEL_TSMF_CLIENT_MEDIA_H */
diff --git a/channels/tsmf/client/tsmf_types.h b/channels/tsmf/client/tsmf_types.h
new file mode 100644
index 0000000..708f208
--- /dev/null
+++ b/channels/tsmf/client/tsmf_types.h
@@ -0,0 +1,61 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - Types
+ *
+ * Copyright 2010-2011 Vic Lee
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_TSMF_CLIENT_TYPES_H
+#define FREERDP_CHANNEL_TSMF_CLIENT_TYPES_H
+
+#include <freerdp/config.h>
+
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("tsmf.client")
+
+#ifdef WITH_DEBUG_TSMF
+#define DEBUG_TSMF(...) WLog_DBG(TAG, __VA_ARGS__)
+#else
+#define DEBUG_TSMF(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+typedef struct
+{
+ int MajorType;
+ int SubType;
+ int FormatType;
+
+ UINT32 Width;
+ UINT32 Height;
+ UINT32 BitRate;
+ struct
+ {
+ UINT32 Numerator;
+ UINT32 Denominator;
+ } SamplesPerSecond;
+ UINT32 Channels;
+ UINT32 BitsPerSample;
+ UINT32 BlockAlign;
+ const BYTE* ExtraData;
+ UINT32 ExtraDataSize;
+} TS_AM_MEDIA_TYPE;
+
+#endif /* FREERDP_CHANNEL_TSMF_CLIENT_TYPES_H */
diff --git a/channels/urbdrc/CMakeLists.txt b/channels/urbdrc/CMakeLists.txt
new file mode 100644
index 0000000..0030e27
--- /dev/null
+++ b/channels/urbdrc/CMakeLists.txt
@@ -0,0 +1,30 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("urbdrc")
+
+include_directories(common)
+add_subdirectory(common)
+
+if(WITH_CLIENT_CHANNELS)
+ option(WITH_DEBUG_URBDRC "Dump data send/received in URBDRC channel" ${DEFAULT_DEBUG_OPTION})
+
+ find_package(libusb-1.0 REQUIRED)
+ include_directories(${LIBUSB_1_INCLUDE_DIRS})
+
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/urbdrc/ChannelOptions.cmake b/channels/urbdrc/ChannelOptions.cmake
new file mode 100644
index 0000000..770ba5e
--- /dev/null
+++ b/channels/urbdrc/ChannelOptions.cmake
@@ -0,0 +1,18 @@
+
+if (IOS OR ANDROID)
+ set(OPTION_DEFAULT OFF)
+ set(OPTION_CLIENT_DEFAULT OFF)
+ set(OPTION_SERVER_DEFAULT OFF)
+else()
+ set(OPTION_DEFAULT ON)
+ set(OPTION_CLIENT_DEFAULT ON)
+ set(OPTION_SERVER_DEFAULT OFF)
+endif()
+
+define_channel_options(NAME "urbdrc" TYPE "dynamic"
+ DESCRIPTION "USB Devices Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPEUSB]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
diff --git a/channels/urbdrc/client/CMakeLists.txt b/channels/urbdrc/client/CMakeLists.txt
new file mode 100644
index 0000000..1787024
--- /dev/null
+++ b/channels/urbdrc/client/CMakeLists.txt
@@ -0,0 +1,40 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Atrust corp.
+# Copyright 2012 Alfred Liu <alfred.liu@atruscorp.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("urbdrc")
+
+set(${MODULE_PREFIX}_SRCS
+ data_transfer.c
+ data_transfer.h
+ urbdrc_main.c
+ urbdrc_main.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ urbdrc-common
+)
+if (UDEV_FOUND AND UDEV_LIBRARIES)
+ list(APPEND ${MODULE_PREFIX}_LIBS ${UDEV_LIBRARIES})
+endif()
+
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
+
+# libusb subsystem
+add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "libusb" "")
diff --git a/channels/urbdrc/client/data_transfer.c b/channels/urbdrc/client/data_transfer.c
new file mode 100644
index 0000000..7a7e5a2
--- /dev/null
+++ b/channels/urbdrc/client/data_transfer.c
@@ -0,0 +1,1949 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX USB Redirection
+ *
+ * Copyright 2012 Atrust corp.
+ * Copyright 2012 Alfred Liu <alfred.liu@atruscorp.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/sysinfo.h>
+
+#include <urbdrc_helpers.h>
+
+#include "urbdrc_types.h"
+#include "data_transfer.h"
+
+static void usb_process_get_port_status(IUDEVICE* pdev, wStream* out)
+{
+ int bcdUSB = pdev->query_device_descriptor(pdev, BCD_USB);
+
+ switch (bcdUSB)
+ {
+ case USB_v1_0:
+ Stream_Write_UINT32(out, 0x303);
+ break;
+
+ case USB_v1_1:
+ Stream_Write_UINT32(out, 0x103);
+ break;
+
+ case USB_v2_0:
+ Stream_Write_UINT32(out, 0x503);
+ break;
+
+ default:
+ Stream_Write_UINT32(out, 0x503);
+ break;
+ }
+}
+
+static UINT urb_write_completion(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, BOOL noAck,
+ wStream* out, UINT32 InterfaceId, UINT32 MessageId,
+ UINT32 RequestId, UINT32 usbd_status, UINT32 OutputBufferSize)
+{
+ if (!out)
+ return ERROR_INVALID_PARAMETER;
+
+ if (Stream_Capacity(out) < OutputBufferSize + 36)
+ {
+ Stream_Free(out, TRUE);
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ Stream_SetPosition(out, 0);
+ Stream_Write_UINT32(out, InterfaceId); /** interface */
+ Stream_Write_UINT32(out, MessageId); /** message id */
+
+ if (OutputBufferSize != 0)
+ Stream_Write_UINT32(out, URB_COMPLETION);
+ else
+ Stream_Write_UINT32(out, URB_COMPLETION_NO_DATA);
+
+ Stream_Write_UINT32(out, RequestId); /** RequestId */
+ Stream_Write_UINT32(out, 8); /** CbTsUrbResult */
+ /** TsUrbResult TS_URB_RESULT_HEADER */
+ Stream_Write_UINT16(out, 8); /** Size */
+ Stream_Write_UINT16(out, 0); /* Padding */
+ Stream_Write_UINT32(out, usbd_status); /** UsbdStatus */
+ Stream_Write_UINT32(out, 0); /** HResult */
+ Stream_Write_UINT32(out, OutputBufferSize); /** OutputBufferSize */
+ Stream_Seek(out, OutputBufferSize);
+
+ if (!noAck)
+ return stream_write_and_free(callback->plugin, callback->channel, out);
+ else
+ Stream_Free(out, TRUE);
+
+ return ERROR_SUCCESS;
+}
+
+static wStream* urb_create_iocompletion(UINT32 InterfaceField, UINT32 MessageId, UINT32 RequestId,
+ UINT32 OutputBufferSize)
+{
+ const UINT32 InterfaceId = (STREAM_ID_PROXY << 30) | (InterfaceField & 0x3FFFFFFF);
+
+#if UINT32_MAX >= SIZE_MAX
+ if (OutputBufferSize > UINT32_MAX - 28ull)
+ return NULL;
+#endif
+
+ wStream* out = Stream_New(NULL, OutputBufferSize + 28ull);
+
+ if (!out)
+ return NULL;
+
+ Stream_Write_UINT32(out, InterfaceId); /** interface */
+ Stream_Write_UINT32(out, MessageId); /** message id */
+ Stream_Write_UINT32(out, IOCONTROL_COMPLETION); /** function id */
+ Stream_Write_UINT32(out, RequestId); /** RequestId */
+ Stream_Write_UINT32(out, USBD_STATUS_SUCCESS); /** HResult */
+ Stream_Write_UINT32(out, OutputBufferSize); /** Information */
+ Stream_Write_UINT32(out, OutputBufferSize); /** OutputBufferSize */
+ return out;
+}
+
+static UINT urbdrc_process_register_request_callback(IUDEVICE* pdev,
+ GENERIC_CHANNEL_CALLBACK* callback, wStream* s,
+ IUDEVMAN* udevman)
+{
+ UINT32 NumRequestCompletion = 0;
+ UINT32 RequestCompletion = 0;
+ URBDRC_PLUGIN* urbdrc = NULL;
+
+ if (!callback || !s || !udevman || !pdev)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+
+ if (!urbdrc)
+ return ERROR_INVALID_PARAMETER;
+
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "urbdrc_process_register_request_callback");
+
+ if (Stream_GetRemainingLength(s) >= 8)
+ {
+ Stream_Read_UINT32(s, NumRequestCompletion); /** must be 1 */
+ /** RequestCompletion:
+ * unique Request Completion interface for the client to use */
+ Stream_Read_UINT32(s, RequestCompletion);
+ pdev->set_ReqCompletion(pdev, RequestCompletion);
+ }
+ else if (Stream_GetRemainingLength(s) >= 4) /** Unregister the device */
+ {
+ Stream_Read_UINT32(s, RequestCompletion);
+
+ if (pdev->get_ReqCompletion(pdev) == RequestCompletion)
+ pdev->setChannelClosed(pdev);
+ }
+ else
+ return ERROR_INVALID_DATA;
+
+ return ERROR_SUCCESS;
+}
+
+static UINT urbdrc_process_cancel_request(IUDEVICE* pdev, wStream* s, IUDEVMAN* udevman)
+{
+ UINT32 CancelId = 0;
+ URBDRC_PLUGIN* urbdrc = NULL;
+
+ if (!s || !udevman || !pdev)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)udevman->plugin;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, CancelId);
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "CANCEL_REQUEST: CancelId=%08" PRIx32 "", CancelId);
+
+ if (pdev->cancel_transfer_request(pdev, CancelId) < 0)
+ return ERROR_INTERNAL_ERROR;
+
+ return ERROR_SUCCESS;
+}
+
+static UINT urbdrc_process_retract_device_request(IUDEVICE* pdev, wStream* s, IUDEVMAN* udevman)
+{
+ UINT32 Reason = 0;
+ URBDRC_PLUGIN* urbdrc = NULL;
+
+ if (!s || !udevman)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)udevman->plugin;
+
+ if (!urbdrc)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, Reason); /** Reason */
+
+ switch (Reason)
+ {
+ case UsbRetractReason_BlockedByPolicy:
+ WLog_Print(urbdrc->log, WLOG_DEBUG,
+ "UsbRetractReason_BlockedByPolicy: now it is not support");
+ return ERROR_ACCESS_DENIED;
+
+ default:
+ WLog_Print(urbdrc->log, WLOG_DEBUG,
+ "urbdrc_process_retract_device_request: Unknown Reason %" PRIu32 "", Reason);
+ return ERROR_ACCESS_DENIED;
+ }
+
+ return ERROR_SUCCESS;
+}
+
+static UINT urbdrc_process_io_control(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback,
+ wStream* s, UINT32 MessageId, IUDEVMAN* udevman)
+{
+ UINT32 InterfaceId = 0;
+ UINT32 IoControlCode = 0;
+ UINT32 InputBufferSize = 0;
+ UINT32 OutputBufferSize = 0;
+ UINT32 RequestId = 0;
+ UINT32 usbd_status = USBD_STATUS_SUCCESS;
+ wStream* out = NULL;
+ int success = 0;
+ URBDRC_PLUGIN* urbdrc = NULL;
+
+ if (!callback || !s || !udevman || !pdev)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+
+ if (!urbdrc)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, IoControlCode);
+ Stream_Read_UINT32(s, InputBufferSize);
+
+ if (!Stream_SafeSeek(s, InputBufferSize))
+ return ERROR_INVALID_DATA;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8ULL))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, OutputBufferSize);
+ Stream_Read_UINT32(s, RequestId);
+
+ if (OutputBufferSize > UINT32_MAX - 4)
+ return ERROR_INVALID_DATA;
+
+ InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev));
+ out = urb_create_iocompletion(InterfaceId, MessageId, RequestId, OutputBufferSize + 4);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ switch (IoControlCode)
+ {
+ case IOCTL_INTERNAL_USB_SUBMIT_URB: /** 0x00220003 */
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "ioctl: IOCTL_INTERNAL_USB_SUBMIT_URB");
+ WLog_Print(urbdrc->log, WLOG_ERROR,
+ " Function IOCTL_INTERNAL_USB_SUBMIT_URB: Unchecked");
+ break;
+
+ case IOCTL_INTERNAL_USB_RESET_PORT: /** 0x00220007 */
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "ioctl: IOCTL_INTERNAL_USB_RESET_PORT");
+ break;
+
+ case IOCTL_INTERNAL_USB_GET_PORT_STATUS: /** 0x00220013 */
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "ioctl: IOCTL_INTERNAL_USB_GET_PORT_STATUS");
+ success = pdev->query_device_port_status(pdev, &usbd_status, &OutputBufferSize,
+ Stream_Pointer(out));
+
+ if (success)
+ {
+ if (!Stream_SafeSeek(out, OutputBufferSize))
+ {
+ Stream_Free(out, TRUE);
+ return ERROR_INVALID_DATA;
+ }
+
+ if (pdev->isExist(pdev) == 0)
+ Stream_Write_UINT32(out, 0);
+ else
+ usb_process_get_port_status(pdev, out);
+ }
+
+ break;
+
+ case IOCTL_INTERNAL_USB_CYCLE_PORT: /** 0x0022001F */
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "ioctl: IOCTL_INTERNAL_USB_CYCLE_PORT");
+ WLog_Print(urbdrc->log, WLOG_ERROR,
+ " Function IOCTL_INTERNAL_USB_CYCLE_PORT: Unchecked");
+ break;
+
+ case IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION: /** 0x00220027 */
+ WLog_Print(urbdrc->log, WLOG_DEBUG,
+ "ioctl: IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION");
+ WLog_Print(urbdrc->log, WLOG_ERROR,
+ " Function IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION: Unchecked");
+ break;
+
+ default:
+ WLog_Print(urbdrc->log, WLOG_DEBUG,
+ "urbdrc_process_io_control: unknown IoControlCode 0x%" PRIX32 "",
+ IoControlCode);
+ Stream_Free(out, TRUE);
+ return ERROR_INVALID_OPERATION;
+ }
+
+ return stream_write_and_free(callback->plugin, callback->channel, out);
+}
+
+static UINT urbdrc_process_internal_io_control(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback,
+ wStream* s, UINT32 MessageId, IUDEVMAN* udevman)
+{
+ wStream* out = NULL;
+ UINT32 IoControlCode = 0;
+ UINT32 InterfaceId = 0;
+ UINT32 InputBufferSize = 0;
+ UINT32 OutputBufferSize = 0;
+ UINT32 RequestId = 0;
+ UINT32 frames = 0;
+
+ if (!pdev || !callback || !s || !udevman)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, IoControlCode);
+ Stream_Read_UINT32(s, InputBufferSize);
+
+ if (!Stream_SafeSeek(s, InputBufferSize))
+ return ERROR_INVALID_DATA;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8ULL))
+ return ERROR_INVALID_DATA;
+ Stream_Read_UINT32(s, OutputBufferSize);
+ Stream_Read_UINT32(s, RequestId);
+ InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev));
+ // TODO: Implement control code.
+ /** Fixme: Currently this is a FALSE bustime... */
+ frames = GetTickCount();
+ out = urb_create_iocompletion(InterfaceId, MessageId, RequestId, 4);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write_UINT32(out, frames); /** OutputBuffer */
+ return stream_write_and_free(callback->plugin, callback->channel, out);
+}
+
+static UINT urbdrc_process_query_device_text(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback,
+ wStream* s, UINT32 MessageId, IUDEVMAN* udevman)
+{
+ UINT32 out_size = 0;
+ UINT32 TextType = 0;
+ UINT32 LocaleId = 0;
+ UINT32 InterfaceId = 0;
+ UINT8 bufferSize = 0xFF;
+ UINT32 hr = 0;
+ wStream* out = NULL;
+ BYTE DeviceDescription[0x100] = { 0 };
+
+ if (!pdev || !callback || !s || !udevman)
+ return ERROR_INVALID_PARAMETER;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, TextType);
+ Stream_Read_UINT32(s, LocaleId);
+ if (LocaleId > UINT16_MAX)
+ return ERROR_INVALID_DATA;
+
+ hr = pdev->control_query_device_text(pdev, TextType, (UINT16)LocaleId, &bufferSize,
+ DeviceDescription);
+ InterfaceId = ((STREAM_ID_STUB << 30) | pdev->get_UsbDevice(pdev));
+ out_size = 16 + bufferSize;
+
+ if (bufferSize != 0)
+ out_size += 2;
+
+ out = Stream_New(NULL, out_size);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write_UINT32(out, InterfaceId); /** interface */
+ Stream_Write_UINT32(out, MessageId); /** message id */
+ Stream_Write_UINT32(out, bufferSize / 2); /** cchDeviceDescription in WCHAR */
+ Stream_Write(out, DeviceDescription, bufferSize); /* '\0' terminated unicode */
+ Stream_Write_UINT32(out, hr); /** HResult */
+ return stream_write_and_free(callback->plugin, callback->channel, out);
+}
+
+static void func_select_all_interface_for_msconfig(IUDEVICE* pdev,
+ MSUSB_CONFIG_DESCRIPTOR* MsConfig)
+{
+ MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces = MsConfig->MsInterfaces;
+ BYTE InterfaceNumber = 0;
+ BYTE AlternateSetting = 0;
+ UINT32 NumInterfaces = MsConfig->NumInterfaces;
+
+ for (UINT32 inum = 0; inum < NumInterfaces; inum++)
+ {
+ InterfaceNumber = MsInterfaces[inum]->InterfaceNumber;
+ AlternateSetting = MsInterfaces[inum]->AlternateSetting;
+ pdev->select_interface(pdev, InterfaceNumber, AlternateSetting);
+ }
+}
+
+static UINT urb_select_configuration(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, wStream* s,
+ UINT32 RequestField, UINT32 MessageId, IUDEVMAN* udevman,
+ int transferDir)
+{
+ MSUSB_CONFIG_DESCRIPTOR* MsConfig = NULL;
+ size_t out_size = 0;
+ UINT32 InterfaceId = 0;
+ UINT32 NumInterfaces = 0;
+ UINT32 usbd_status = 0;
+ BYTE ConfigurationDescriptorIsValid = 0;
+ wStream* out = NULL;
+ int MsOutSize = 0;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ const BOOL noAck = (RequestField & 0x80000000U) != 0;
+ const UINT32 RequestId = RequestField & 0x7FFFFFFF;
+
+ if (!callback || !s || !udevman || !pdev)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+
+ if (!urbdrc)
+ return ERROR_INVALID_PARAMETER;
+
+ if (transferDir == 0)
+ {
+ WLog_Print(urbdrc->log, WLOG_ERROR, "urb_select_configuration: unsupported transfer out");
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev));
+ Stream_Read_UINT8(s, ConfigurationDescriptorIsValid);
+ Stream_Seek(s, 3); /* Padding */
+ Stream_Read_UINT32(s, NumInterfaces);
+
+ /** if ConfigurationDescriptorIsValid is zero, then just do nothing.*/
+ if (ConfigurationDescriptorIsValid)
+ {
+ /* parser data for struct config */
+ MsConfig = msusb_msconfig_read(s, NumInterfaces);
+
+ if (!MsConfig)
+ return ERROR_INVALID_DATA;
+
+ /* select config */
+ pdev->select_configuration(pdev, MsConfig->bConfigurationValue);
+ /* select all interface */
+ func_select_all_interface_for_msconfig(pdev, MsConfig);
+ /* complete configuration setup */
+ if (!pdev->complete_msconfig_setup(pdev, MsConfig))
+ {
+ msusb_msconfig_free(MsConfig);
+ MsConfig = NULL;
+ }
+ }
+
+ if (MsConfig)
+ MsOutSize = MsConfig->MsOutSize;
+
+ if (MsOutSize > 0)
+ {
+ if ((size_t)MsOutSize > SIZE_MAX - 36)
+ return ERROR_INVALID_DATA;
+
+ out_size = 36 + MsOutSize;
+ }
+ else
+ out_size = 44;
+
+ out = Stream_New(NULL, out_size);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write_UINT32(out, InterfaceId); /** interface */
+ Stream_Write_UINT32(out, MessageId); /** message id */
+ Stream_Write_UINT32(out, URB_COMPLETION_NO_DATA); /** function id */
+ Stream_Write_UINT32(out, RequestId); /** RequestId */
+
+ if (MsOutSize > 0)
+ {
+ /** CbTsUrbResult */
+ Stream_Write_UINT32(out, 8 + MsOutSize);
+ /** TS_URB_RESULT_HEADER Size*/
+ Stream_Write_UINT16(out, 8 + MsOutSize);
+ }
+ else
+ {
+ Stream_Write_UINT32(out, 16);
+ Stream_Write_UINT16(out, 16);
+ }
+
+ /** Padding, MUST be ignored upon receipt */
+ Stream_Write_UINT16(out, TS_URB_SELECT_CONFIGURATION);
+ Stream_Write_UINT32(out, usbd_status); /** UsbdStatus */
+
+ /** TS_URB_SELECT_CONFIGURATION_RESULT */
+ if (MsOutSize > 0)
+ msusb_msconfig_write(MsConfig, out);
+ else
+ {
+ Stream_Write_UINT32(out, 0); /** ConfigurationHandle */
+ Stream_Write_UINT32(out, NumInterfaces); /** NumInterfaces */
+ }
+
+ Stream_Write_UINT32(out, 0); /** HResult */
+ Stream_Write_UINT32(out, 0); /** OutputBufferSize */
+
+ if (!noAck)
+ return stream_write_and_free(callback->plugin, callback->channel, out);
+ else
+ Stream_Free(out, TRUE);
+
+ return ERROR_SUCCESS;
+}
+
+static UINT urb_select_interface(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, wStream* s,
+ UINT32 RequestField, UINT32 MessageId, IUDEVMAN* udevman,
+ int transferDir)
+{
+ MSUSB_CONFIG_DESCRIPTOR* MsConfig = NULL;
+ MSUSB_INTERFACE_DESCRIPTOR* MsInterface = NULL;
+ UINT32 out_size = 0;
+ UINT32 InterfaceId = 0;
+ UINT32 ConfigurationHandle = 0;
+ UINT32 OutputBufferSize = 0;
+ BYTE InterfaceNumber = 0;
+ wStream* out = NULL;
+ UINT32 interface_size = 0;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ const BOOL noAck = (RequestField & 0x80000000U) != 0;
+ const UINT32 RequestId = RequestField & 0x7FFFFFFF;
+
+ if (!callback || !s || !udevman || !pdev)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+
+ if (!urbdrc)
+ return ERROR_INVALID_PARAMETER;
+
+ if (transferDir == 0)
+ {
+ WLog_Print(urbdrc->log, WLOG_ERROR, "urb_select_interface: not support transfer out");
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev));
+ Stream_Read_UINT32(s, ConfigurationHandle);
+ MsInterface = msusb_msinterface_read(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4) || !MsInterface)
+ {
+ msusb_msinterface_free(MsInterface);
+ return ERROR_INVALID_DATA;
+ }
+
+ Stream_Read_UINT32(s, OutputBufferSize);
+ pdev->select_interface(pdev, MsInterface->InterfaceNumber, MsInterface->AlternateSetting);
+ /* replace device's MsInterface */
+ MsConfig = pdev->get_MsConfig(pdev);
+ InterfaceNumber = MsInterface->InterfaceNumber;
+ if (!msusb_msinterface_replace(MsConfig, InterfaceNumber, MsInterface))
+ {
+ msusb_msconfig_free(MsConfig);
+ return ERROR_BAD_CONFIGURATION;
+ }
+ /* complete configuration setup */
+ if (!pdev->complete_msconfig_setup(pdev, MsConfig))
+ {
+ msusb_msconfig_free(MsConfig);
+ return ERROR_BAD_CONFIGURATION;
+ }
+ MsInterface = MsConfig->MsInterfaces[InterfaceNumber];
+ interface_size = 16 + (MsInterface->NumberOfPipes * 20);
+ out_size = 36 + interface_size;
+ out = Stream_New(NULL, out_size);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write_UINT32(out, InterfaceId); /** interface */
+ Stream_Write_UINT32(out, MessageId); /** message id */
+ Stream_Write_UINT32(out, URB_COMPLETION_NO_DATA); /** function id */
+ Stream_Write_UINT32(out, RequestId); /** RequestId */
+ Stream_Write_UINT32(out, 8 + interface_size); /** CbTsUrbResult */
+ /** TS_URB_RESULT_HEADER */
+ Stream_Write_UINT16(out, 8 + interface_size); /** Size */
+ /** Padding, MUST be ignored upon receipt */
+ Stream_Write_UINT16(out, TS_URB_SELECT_INTERFACE);
+ Stream_Write_UINT32(out, USBD_STATUS_SUCCESS); /** UsbdStatus */
+ /** TS_URB_SELECT_INTERFACE_RESULT */
+ msusb_msinterface_write(MsInterface, out);
+ Stream_Write_UINT32(out, 0); /** HResult */
+ Stream_Write_UINT32(out, 0); /** OutputBufferSize */
+
+ if (!noAck)
+ return stream_write_and_free(callback->plugin, callback->channel, out);
+ else
+ Stream_Free(out, TRUE);
+
+ return ERROR_SUCCESS;
+}
+
+static UINT urb_control_transfer(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, wStream* s,
+ UINT32 RequestField, UINT32 MessageId, IUDEVMAN* udevman,
+ int transferDir, int External)
+{
+ UINT32 out_size = 0;
+ UINT32 InterfaceId = 0;
+ UINT32 EndpointAddress = 0;
+ UINT32 PipeHandle = 0;
+ UINT32 TransferFlags = 0;
+ UINT32 OutputBufferSize = 0;
+ UINT32 usbd_status = 0;
+ UINT32 Timeout = 0;
+ BYTE bmRequestType = 0;
+ BYTE Request = 0;
+ UINT16 Value = 0;
+ UINT16 Index = 0;
+ UINT16 length = 0;
+ BYTE* buffer = NULL;
+ wStream* out = NULL;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ const BOOL noAck = (RequestField & 0x80000000U) != 0;
+ const UINT32 RequestId = RequestField & 0x7FFFFFFF;
+
+ if (!callback || !s || !udevman || !pdev)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+
+ if (!urbdrc)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev));
+ Stream_Read_UINT32(s, PipeHandle);
+ Stream_Read_UINT32(s, TransferFlags); /** TransferFlags */
+ EndpointAddress = (PipeHandle & 0x000000ff);
+ Timeout = 2000;
+
+ switch (External)
+ {
+ case URB_CONTROL_TRANSFER_EXTERNAL:
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, Timeout); /** TransferFlags */
+ break;
+
+ case URB_CONTROL_TRANSFER_NONEXTERNAL:
+ break;
+ }
+
+ /** SetupPacket 8 bytes */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT8(s, bmRequestType);
+ Stream_Read_UINT8(s, Request);
+ Stream_Read_UINT16(s, Value);
+ Stream_Read_UINT16(s, Index);
+ Stream_Read_UINT16(s, length);
+ Stream_Read_UINT32(s, OutputBufferSize);
+
+ if (length != OutputBufferSize)
+ {
+ WLog_Print(urbdrc->log, WLOG_ERROR, "urb_control_transfer ERROR: buf != length");
+ return ERROR_INVALID_DATA;
+ }
+
+ out_size = 36 + OutputBufferSize;
+ out = Stream_New(NULL, out_size);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Seek(out, 36);
+ /** Get Buffer Data */
+ buffer = Stream_Pointer(out);
+
+ if (transferDir == USBD_TRANSFER_DIRECTION_OUT)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, OutputBufferSize))
+ return ERROR_INVALID_DATA;
+ Stream_Copy(s, out, OutputBufferSize);
+ }
+
+ /** process TS_URB_CONTROL_TRANSFER */
+ if (!pdev->control_transfer(pdev, RequestId, EndpointAddress, TransferFlags, bmRequestType,
+ Request, Value, Index, &usbd_status, &OutputBufferSize, buffer,
+ Timeout))
+ {
+ WLog_Print(urbdrc->log, WLOG_ERROR, "control_transfer failed");
+ Stream_Free(out, TRUE);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId,
+ usbd_status, OutputBufferSize);
+}
+
+static void urb_bulk_transfer_cb(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, wStream* out,
+ UINT32 InterfaceId, BOOL noAck, UINT32 MessageId, UINT32 RequestId,
+ UINT32 NumberOfPackets, UINT32 status, UINT32 StartFrame,
+ UINT32 ErrorCount, UINT32 OutputBufferSize)
+{
+ if (!pdev->isChannelClosed(pdev))
+ urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, status,
+ OutputBufferSize);
+ else
+ Stream_Free(out, TRUE);
+}
+
+static UINT urb_bulk_or_interrupt_transfer(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback,
+ wStream* s, UINT32 RequestField, UINT32 MessageId,
+ IUDEVMAN* udevman, int transferDir)
+{
+ UINT32 EndpointAddress = 0;
+ UINT32 PipeHandle = 0;
+ UINT32 TransferFlags = 0;
+ UINT32 OutputBufferSize = 0;
+ const BOOL noAck = (RequestField & 0x80000000U) != 0;
+ const UINT32 RequestId = RequestField & 0x7FFFFFFF;
+
+ if (!pdev || !callback || !s || !udevman)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, PipeHandle);
+ Stream_Read_UINT32(s, TransferFlags); /** TransferFlags */
+ Stream_Read_UINT32(s, OutputBufferSize);
+ EndpointAddress = (PipeHandle & 0x000000ff);
+
+ if (transferDir == USBD_TRANSFER_DIRECTION_OUT)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, OutputBufferSize))
+ {
+ return ERROR_INVALID_DATA;
+ }
+ }
+
+ /** process TS_URB_BULK_OR_INTERRUPT_TRANSFER */
+ return pdev->bulk_or_interrupt_transfer(
+ pdev, callback, MessageId, RequestId, EndpointAddress, TransferFlags, noAck,
+ OutputBufferSize, (transferDir == USBD_TRANSFER_DIRECTION_OUT) ? Stream_Pointer(s) : NULL,
+ urb_bulk_transfer_cb, 10000);
+}
+
+static void urb_isoch_transfer_cb(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, wStream* out,
+ UINT32 InterfaceId, BOOL noAck, UINT32 MessageId,
+ UINT32 RequestId, UINT32 NumberOfPackets, UINT32 status,
+ UINT32 StartFrame, UINT32 ErrorCount, UINT32 OutputBufferSize)
+{
+ if (!noAck)
+ {
+ UINT32 packetSize = (status == 0) ? NumberOfPackets * 12 : 0;
+ Stream_SetPosition(out, 0);
+ /* fill the send data */
+ Stream_Write_UINT32(out, InterfaceId); /** interface */
+ Stream_Write_UINT32(out, MessageId); /** message id */
+
+ if (OutputBufferSize == 0)
+ Stream_Write_UINT32(out, URB_COMPLETION_NO_DATA); /** function id */
+ else
+ Stream_Write_UINT32(out, URB_COMPLETION); /** function id */
+
+ Stream_Write_UINT32(out, RequestId); /** RequestId */
+ Stream_Write_UINT32(out, 20 + packetSize); /** CbTsUrbResult */
+ /** TsUrbResult TS_URB_RESULT_HEADER */
+ Stream_Write_UINT16(out, 20 + packetSize); /** Size */
+ Stream_Write_UINT16(out, 0); /* Padding */
+ Stream_Write_UINT32(out, status); /** UsbdStatus */
+ Stream_Write_UINT32(out, StartFrame); /** StartFrame */
+
+ if (status == 0)
+ {
+ /** NumberOfPackets */
+ Stream_Write_UINT32(out, NumberOfPackets);
+ Stream_Write_UINT32(out, ErrorCount); /** ErrorCount */
+ Stream_Seek(out, packetSize);
+ }
+ else
+ {
+ Stream_Write_UINT32(out, 0); /** NumberOfPackets */
+ Stream_Write_UINT32(out, ErrorCount); /** ErrorCount */
+ }
+
+ Stream_Write_UINT32(out, 0); /** HResult */
+ Stream_Write_UINT32(out, OutputBufferSize); /** OutputBufferSize */
+ Stream_Seek(out, OutputBufferSize);
+
+ stream_write_and_free(callback->plugin, callback->channel, out);
+ }
+}
+
+static UINT urb_isoch_transfer(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, wStream* s,
+ UINT32 RequestField, UINT32 MessageId, IUDEVMAN* udevman,
+ int transferDir)
+{
+ int rc = 0;
+ UINT32 EndpointAddress = 0;
+ UINT32 PipeHandle = 0;
+ UINT32 TransferFlags = 0;
+ UINT32 StartFrame = 0;
+ UINT32 NumberOfPackets = 0;
+ UINT32 ErrorCount = 0;
+ UINT32 OutputBufferSize = 0;
+ BYTE* packetDescriptorData = NULL;
+ const BOOL noAck = (RequestField & 0x80000000U) != 0;
+ const UINT32 RequestId = RequestField & 0x7FFFFFFF;
+
+ if (!pdev || !callback || !udevman)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 20))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, PipeHandle);
+ EndpointAddress = (PipeHandle & 0x000000ff);
+ Stream_Read_UINT32(s, TransferFlags); /** TransferFlags */
+ Stream_Read_UINT32(s, StartFrame); /** StartFrame */
+ Stream_Read_UINT32(s, NumberOfPackets); /** NumberOfPackets */
+ Stream_Read_UINT32(s, ErrorCount); /** ErrorCount */
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, NumberOfPackets, 12ull))
+ return ERROR_INVALID_DATA;
+
+ packetDescriptorData = Stream_Pointer(s);
+ Stream_Seek(s, NumberOfPackets * 12);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(UINT32)))
+ return ERROR_INVALID_DATA;
+ Stream_Read_UINT32(s, OutputBufferSize);
+
+ if (transferDir == USBD_TRANSFER_DIRECTION_OUT)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, OutputBufferSize))
+ return ERROR_INVALID_DATA;
+ }
+
+ rc = pdev->isoch_transfer(
+ pdev, callback, MessageId, RequestId, EndpointAddress, TransferFlags, StartFrame,
+ ErrorCount, noAck, packetDescriptorData, NumberOfPackets, OutputBufferSize,
+ (transferDir == USBD_TRANSFER_DIRECTION_OUT) ? Stream_Pointer(s) : NULL,
+ urb_isoch_transfer_cb, 2000);
+
+ if (rc < 0)
+ return ERROR_INTERNAL_ERROR;
+ return (UINT)rc;
+}
+
+static UINT urb_control_descriptor_request(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback,
+ wStream* s, UINT32 RequestField, UINT32 MessageId,
+ IUDEVMAN* udevman, BYTE func_recipient, int transferDir)
+{
+ size_t out_size = 0;
+ UINT32 InterfaceId = 0;
+ UINT32 OutputBufferSize = 0;
+ UINT32 usbd_status = 0;
+ BYTE bmRequestType = 0;
+ BYTE desc_index = 0;
+ BYTE desc_type = 0;
+ UINT16 langId = 0;
+ wStream* out = NULL;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ const BOOL noAck = (RequestField & 0x80000000U) != 0;
+ const UINT32 RequestId = RequestField & 0x7FFFFFFF;
+
+ if (!callback || !s || !udevman || !pdev)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+
+ if (!urbdrc)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev));
+ Stream_Read_UINT8(s, desc_index);
+ Stream_Read_UINT8(s, desc_type);
+ Stream_Read_UINT16(s, langId);
+ Stream_Read_UINT32(s, OutputBufferSize);
+ if (OutputBufferSize > UINT32_MAX - 36)
+ return ERROR_INVALID_DATA;
+ if (transferDir == USBD_TRANSFER_DIRECTION_OUT)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, OutputBufferSize))
+ return ERROR_INVALID_DATA;
+ }
+
+ out_size = 36ULL + OutputBufferSize;
+ out = Stream_New(NULL, out_size);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Seek(out, 36);
+ bmRequestType = func_recipient;
+
+ switch (transferDir)
+ {
+ case USBD_TRANSFER_DIRECTION_IN:
+ bmRequestType |= 0x80;
+ break;
+
+ case USBD_TRANSFER_DIRECTION_OUT:
+ bmRequestType |= 0x00;
+ Stream_Copy(s, out, OutputBufferSize);
+ Stream_Rewind(out, OutputBufferSize);
+ break;
+
+ default:
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "get error transferDir");
+ OutputBufferSize = 0;
+ usbd_status = USBD_STATUS_STALL_PID;
+ break;
+ }
+
+ /** process get usb device descriptor */
+ if (!pdev->control_transfer(pdev, RequestId, 0, 0, bmRequestType,
+ 0x06, /* REQUEST_GET_DESCRIPTOR */
+ (desc_type << 8) | desc_index, langId, &usbd_status,
+ &OutputBufferSize, Stream_Pointer(out), 1000))
+ {
+ WLog_Print(urbdrc->log, WLOG_ERROR, "get_descriptor failed");
+ Stream_Free(out, TRUE);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId,
+ usbd_status, OutputBufferSize);
+}
+
+static UINT urb_control_get_status_request(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback,
+ wStream* s, UINT32 RequestField, UINT32 MessageId,
+ IUDEVMAN* udevman, BYTE func_recipient, int transferDir)
+{
+ size_t out_size = 0;
+ UINT32 InterfaceId = 0;
+ UINT32 OutputBufferSize = 0;
+ UINT32 usbd_status = 0;
+ UINT16 Index = 0;
+ BYTE bmRequestType = 0;
+ wStream* out = NULL;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ const BOOL noAck = (RequestField & 0x80000000U) != 0;
+ const UINT32 RequestId = RequestField & 0x7FFFFFFF;
+
+ if (!callback || !s || !udevman || !pdev)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+
+ if (!urbdrc)
+ return ERROR_INVALID_PARAMETER;
+
+ if (transferDir == 0)
+ {
+ WLog_Print(urbdrc->log, WLOG_DEBUG,
+ "urb_control_get_status_request: transfer out not supported");
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev));
+ Stream_Read_UINT16(s, Index); /** Index */
+ Stream_Seek(s, 2);
+ Stream_Read_UINT32(s, OutputBufferSize);
+ if (OutputBufferSize > UINT32_MAX - 36)
+ return ERROR_INVALID_DATA;
+ out_size = 36ULL + OutputBufferSize;
+ out = Stream_New(NULL, out_size);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Seek(out, 36);
+ bmRequestType = func_recipient | 0x80;
+
+ if (!pdev->control_transfer(pdev, RequestId, 0, 0, bmRequestType, 0x00, /* REQUEST_GET_STATUS */
+ 0, Index, &usbd_status, &OutputBufferSize, Stream_Pointer(out),
+ 1000))
+ {
+ WLog_Print(urbdrc->log, WLOG_ERROR, "control_transfer failed");
+ Stream_Free(out, TRUE);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId,
+ usbd_status, OutputBufferSize);
+}
+
+static UINT urb_control_vendor_or_class_request(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback,
+ wStream* s, UINT32 RequestField, UINT32 MessageId,
+ IUDEVMAN* udevman, BYTE func_type,
+ BYTE func_recipient, int transferDir)
+{
+ UINT32 out_size = 0;
+ UINT32 InterfaceId = 0;
+ UINT32 TransferFlags = 0;
+ UINT32 usbd_status = 0;
+ UINT32 OutputBufferSize = 0;
+ BYTE ReqTypeReservedBits = 0;
+ BYTE Request = 0;
+ BYTE bmRequestType = 0;
+ UINT16 Value = 0;
+ UINT16 Index = 0;
+ UINT16 Padding = 0;
+ wStream* out = NULL;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ const BOOL noAck = (RequestField & 0x80000000U) != 0;
+ const UINT32 RequestId = RequestField & 0x7FFFFFFF;
+
+ if (!callback || !s || !udevman || !pdev)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+
+ if (!urbdrc)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 16))
+ return ERROR_INVALID_DATA;
+
+ InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev));
+ Stream_Read_UINT32(s, TransferFlags); /** TransferFlags */
+ Stream_Read_UINT8(s, ReqTypeReservedBits); /** ReqTypeReservedBids */
+ Stream_Read_UINT8(s, Request); /** Request */
+ Stream_Read_UINT16(s, Value); /** value */
+ Stream_Read_UINT16(s, Index); /** index */
+ Stream_Read_UINT16(s, Padding); /** Padding */
+ Stream_Read_UINT32(s, OutputBufferSize);
+ if (OutputBufferSize > UINT32_MAX - 36)
+ return ERROR_INVALID_DATA;
+
+ if (transferDir == USBD_TRANSFER_DIRECTION_OUT)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, OutputBufferSize))
+ return ERROR_INVALID_DATA;
+ }
+
+ out_size = 36ULL + OutputBufferSize;
+ out = Stream_New(NULL, out_size);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Seek(out, 36);
+
+ /** Get Buffer */
+ if (transferDir == USBD_TRANSFER_DIRECTION_OUT)
+ {
+ Stream_Copy(s, out, OutputBufferSize);
+ Stream_Rewind(out, OutputBufferSize);
+ }
+
+ /** vendor or class command */
+ bmRequestType = func_type | func_recipient;
+
+ if (TransferFlags & USBD_TRANSFER_DIRECTION)
+ bmRequestType |= 0x80;
+
+ WLog_Print(urbdrc->log, WLOG_DEBUG,
+ "RequestId 0x%" PRIx32 " TransferFlags: 0x%" PRIx32 " ReqTypeReservedBits: 0x%" PRIx8
+ " "
+ "Request:0x%" PRIx8 " Value: 0x%" PRIx16 " Index: 0x%" PRIx16
+ " OutputBufferSize: 0x%" PRIx32 " bmRequestType: 0x%" PRIx8,
+ RequestId, TransferFlags, ReqTypeReservedBits, Request, Value, Index,
+ OutputBufferSize, bmRequestType);
+
+ if (!pdev->control_transfer(pdev, RequestId, 0, 0, bmRequestType, Request, Value, Index,
+ &usbd_status, &OutputBufferSize, Stream_Pointer(out), 2000))
+ {
+ WLog_Print(urbdrc->log, WLOG_ERROR, "control_transfer failed");
+ Stream_Free(out, TRUE);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId,
+ usbd_status, OutputBufferSize);
+}
+
+static UINT urb_os_feature_descriptor_request(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback,
+ wStream* s, UINT32 RequestField, UINT32 MessageId,
+ IUDEVMAN* udevman, int transferDir)
+{
+ size_t out_size = 0;
+ UINT32 InterfaceId = 0;
+ UINT32 OutputBufferSize = 0;
+ UINT32 usbd_status = 0;
+ BYTE Recipient = 0;
+ BYTE InterfaceNumber = 0;
+ BYTE Ms_PageIndex = 0;
+ UINT16 Ms_featureDescIndex = 0;
+ wStream* out = NULL;
+ int ret = 0;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ const BOOL noAck = (RequestField & 0x80000000U) != 0;
+ const UINT32 RequestId = RequestField & 0x7FFFFFFF;
+
+ if (!callback || !s || !udevman || !pdev)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+
+ if (!urbdrc)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return ERROR_INVALID_DATA;
+
+ /* 2.2.9.15 TS_URB_OS_FEATURE_DESCRIPTOR_REQUEST */
+ Stream_Read_UINT8(s, Recipient); /** Recipient */
+ Recipient = (Recipient & 0x1f); /* Mask out Padding1 */
+ Stream_Read_UINT8(s, InterfaceNumber); /** InterfaceNumber */
+ Stream_Read_UINT8(s, Ms_PageIndex); /** Ms_PageIndex */
+ Stream_Read_UINT16(s, Ms_featureDescIndex); /** Ms_featureDescIndex */
+ Stream_Seek(s, 3); /* Padding 2 */
+ Stream_Read_UINT32(s, OutputBufferSize);
+ if (OutputBufferSize > UINT32_MAX - 36)
+ return ERROR_INVALID_DATA;
+
+ switch (transferDir)
+ {
+ case USBD_TRANSFER_DIRECTION_OUT:
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, OutputBufferSize))
+ return ERROR_INVALID_DATA;
+
+ break;
+
+ default:
+ break;
+ }
+
+ InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev));
+ out_size = 36ULL + OutputBufferSize;
+ out = Stream_New(NULL, out_size);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Seek(out, 36);
+
+ switch (transferDir)
+ {
+ case USBD_TRANSFER_DIRECTION_OUT:
+ Stream_Copy(s, out, OutputBufferSize);
+ Stream_Rewind(out, OutputBufferSize);
+ break;
+
+ case USBD_TRANSFER_DIRECTION_IN:
+ break;
+ }
+
+ WLog_Print(urbdrc->log, WLOG_DEBUG,
+ "Ms descriptor arg: Recipient:0x%" PRIx8 ", "
+ "InterfaceNumber:0x%" PRIx8 ", Ms_PageIndex:0x%" PRIx8 ", "
+ "Ms_featureDescIndex:0x%" PRIx16 ", OutputBufferSize:0x%" PRIx32 "",
+ Recipient, InterfaceNumber, Ms_PageIndex, Ms_featureDescIndex, OutputBufferSize);
+ /** get ms string */
+ ret = pdev->os_feature_descriptor_request(pdev, RequestId, Recipient, InterfaceNumber,
+ Ms_PageIndex, Ms_featureDescIndex, &usbd_status,
+ &OutputBufferSize, Stream_Pointer(out), 1000);
+
+ if (ret < 0)
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "os_feature_descriptor_request: error num %d", ret);
+
+ return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId,
+ usbd_status, OutputBufferSize);
+}
+
+static UINT urb_pipe_request(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, wStream* s,
+ UINT32 RequestField, UINT32 MessageId, IUDEVMAN* udevman,
+ int transferDir, int action)
+{
+ UINT32 out_size = 0;
+ UINT32 InterfaceId = 0;
+ UINT32 PipeHandle = 0;
+ UINT32 EndpointAddress = 0;
+ UINT32 OutputBufferSize = 0;
+ UINT32 usbd_status = 0;
+ wStream* out = NULL;
+ UINT32 ret = USBD_STATUS_REQUEST_FAILED;
+ int rc = 0;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ const BOOL noAck = (RequestField & 0x80000000U) != 0;
+ const UINT32 RequestId = RequestField & 0x7FFFFFFF;
+
+ if (!callback || !s || !udevman || !pdev)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+
+ if (!urbdrc)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ if (transferDir == 0)
+ {
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "urb_pipe_request: not support transfer out");
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev));
+ Stream_Read_UINT32(s, PipeHandle); /** PipeHandle */
+ Stream_Read_UINT32(s, OutputBufferSize);
+ EndpointAddress = (PipeHandle & 0x000000ff);
+
+ switch (action)
+ {
+ case PIPE_CANCEL:
+ rc = pdev->control_pipe_request(pdev, RequestId, EndpointAddress, &usbd_status,
+ PIPE_CANCEL);
+
+ if (rc < 0)
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "PIPE SET HALT: error %d", ret);
+ else
+ ret = USBD_STATUS_SUCCESS;
+
+ break;
+
+ case PIPE_RESET:
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "urb_pipe_request: PIPE_RESET ep 0x%" PRIx32 "",
+ EndpointAddress);
+ rc = pdev->control_pipe_request(pdev, RequestId, EndpointAddress, &usbd_status,
+ PIPE_RESET);
+
+ if (rc < 0)
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "PIPE RESET: error %d", ret);
+ else
+ ret = USBD_STATUS_SUCCESS;
+
+ break;
+
+ default:
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "urb_pipe_request action: %d not supported",
+ action);
+ ret = USBD_STATUS_INVALID_URB_FUNCTION;
+ break;
+ }
+
+ /** send data */
+ out_size = 36;
+ out = Stream_New(NULL, out_size);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, ret,
+ 0);
+}
+
+static UINT urb_get_current_frame_number(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback,
+ wStream* s, UINT32 RequestField, UINT32 MessageId,
+ IUDEVMAN* udevman, int transferDir)
+{
+ UINT32 out_size = 0;
+ UINT32 InterfaceId = 0;
+ UINT32 OutputBufferSize = 0;
+ UINT32 dummy_frames = 0;
+ wStream* out = NULL;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ const BOOL noAck = (RequestField & 0x80000000U) != 0;
+ const UINT32 RequestId = RequestField & 0x7FFFFFFF;
+
+ if (!callback || !s || !udevman || !pdev)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+
+ if (!urbdrc)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ if (transferDir == 0)
+ {
+ WLog_Print(urbdrc->log, WLOG_DEBUG,
+ "urb_get_current_frame_number: not support transfer out");
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev));
+ Stream_Read_UINT32(s, OutputBufferSize);
+ /** Fixme: Need to fill actual frame number!!*/
+ dummy_frames = GetTickCount();
+ out_size = 40;
+ out = Stream_New(NULL, out_size);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write_UINT32(out, InterfaceId); /** interface */
+ Stream_Write_UINT32(out, MessageId); /** message id */
+ Stream_Write_UINT32(out, URB_COMPLETION_NO_DATA);
+ Stream_Write_UINT32(out, RequestId); /** RequestId */
+ Stream_Write_UINT32(out, 12); /** CbTsUrbResult */
+ /** TsUrbResult TS_URB_RESULT_HEADER */
+ Stream_Write_UINT16(out, 12); /** Size */
+ /** Padding, MUST be ignored upon receipt */
+ Stream_Write_UINT16(out, TS_URB_GET_CURRENT_FRAME_NUMBER);
+ Stream_Write_UINT32(out, USBD_STATUS_SUCCESS); /** UsbdStatus */
+ Stream_Write_UINT32(out, dummy_frames); /** FrameNumber */
+ Stream_Write_UINT32(out, 0); /** HResult */
+ Stream_Write_UINT32(out, 0); /** OutputBufferSize */
+
+ if (!noAck)
+ return stream_write_and_free(callback->plugin, callback->channel, out);
+ else
+ Stream_Free(out, TRUE);
+
+ return ERROR_SUCCESS;
+}
+
+/* Unused function for current server */
+static UINT urb_control_get_configuration_request(IUDEVICE* pdev,
+ GENERIC_CHANNEL_CALLBACK* callback, wStream* s,
+ UINT32 RequestField, UINT32 MessageId,
+ IUDEVMAN* udevman, int transferDir)
+{
+ size_t out_size = 0;
+ UINT32 InterfaceId = 0;
+ UINT32 OutputBufferSize = 0;
+ UINT32 usbd_status = 0;
+ wStream* out = NULL;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ const BOOL noAck = (RequestField & 0x80000000U) != 0;
+ const UINT32 RequestId = RequestField & 0x7FFFFFFF;
+
+ if (!callback || !s || !udevman || !pdev)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+
+ if (!urbdrc)
+ return ERROR_INVALID_PARAMETER;
+
+ if (transferDir == 0)
+ {
+ WLog_Print(urbdrc->log, WLOG_DEBUG,
+ "urb_control_get_configuration_request:"
+ " not support transfer out");
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, OutputBufferSize);
+ if (OutputBufferSize > UINT32_MAX - 36)
+ return ERROR_INVALID_DATA;
+ out_size = 36ULL + OutputBufferSize;
+ out = Stream_New(NULL, out_size);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Seek(out, 36);
+ InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev));
+
+ if (!pdev->control_transfer(pdev, RequestId, 0, 0, 0x80 | 0x00,
+ 0x08, /* REQUEST_GET_CONFIGURATION */
+ 0, 0, &usbd_status, &OutputBufferSize, Stream_Pointer(out), 1000))
+ {
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "control_transfer failed");
+ Stream_Free(out, TRUE);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId,
+ usbd_status, OutputBufferSize);
+}
+
+/* Unused function for current server */
+static UINT urb_control_get_interface_request(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback,
+ wStream* s, UINT32 RequestField, UINT32 MessageId,
+ IUDEVMAN* udevman, int transferDir)
+{
+ size_t out_size = 0;
+ UINT32 InterfaceId = 0;
+ UINT32 OutputBufferSize = 0;
+ UINT32 usbd_status = 0;
+ UINT16 InterfaceNr = 0;
+ wStream* out = NULL;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ const BOOL noAck = (RequestField & 0x80000000U) != 0;
+ const UINT32 RequestId = RequestField & 0x7FFFFFFF;
+
+ if (!callback || !s || !udevman || !pdev)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+
+ if (!urbdrc)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ if (transferDir == 0)
+ {
+ WLog_Print(urbdrc->log, WLOG_DEBUG,
+ "urb_control_get_interface_request: not support transfer out");
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev));
+ Stream_Read_UINT16(s, InterfaceNr);
+ Stream_Seek(s, 2);
+ Stream_Read_UINT32(s, OutputBufferSize);
+ if (OutputBufferSize > UINT32_MAX - 36)
+ return ERROR_INVALID_DATA;
+ out_size = 36ULL + OutputBufferSize;
+ out = Stream_New(NULL, out_size);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Seek(out, 36);
+
+ if (!pdev->control_transfer(
+ pdev, RequestId, 0, 0, 0x80 | 0x01, 0x0A, /* REQUEST_GET_INTERFACE */
+ 0, InterfaceNr, &usbd_status, &OutputBufferSize, Stream_Pointer(out), 1000))
+ {
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "control_transfer failed");
+ Stream_Free(out, TRUE);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId,
+ usbd_status, OutputBufferSize);
+}
+
+static UINT urb_control_feature_request(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback,
+ wStream* s, UINT32 RequestField, UINT32 MessageId,
+ IUDEVMAN* udevman, BYTE func_recipient, BYTE command,
+ int transferDir)
+{
+ UINT32 InterfaceId = 0;
+ UINT32 OutputBufferSize = 0;
+ UINT32 usbd_status = 0;
+ UINT16 FeatureSelector = 0;
+ UINT16 Index = 0;
+ BYTE bmRequestType = 0;
+ BYTE bmRequest = 0;
+ wStream* out = NULL;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ const BOOL noAck = (RequestField & 0x80000000U) != 0;
+ const UINT32 RequestId = RequestField & 0x7FFFFFFF;
+
+ if (!callback || !s || !udevman || !pdev)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+
+ if (!urbdrc)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev));
+ Stream_Read_UINT16(s, FeatureSelector);
+ Stream_Read_UINT16(s, Index);
+ Stream_Read_UINT32(s, OutputBufferSize);
+ if (OutputBufferSize > UINT32_MAX - 36)
+ return ERROR_INVALID_DATA;
+ switch (transferDir)
+ {
+ case USBD_TRANSFER_DIRECTION_OUT:
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, OutputBufferSize))
+ return ERROR_INVALID_DATA;
+
+ break;
+
+ default:
+ break;
+ }
+
+ out = Stream_New(NULL, 36ULL + OutputBufferSize);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Seek(out, 36);
+ bmRequestType = func_recipient;
+
+ switch (transferDir)
+ {
+ case USBD_TRANSFER_DIRECTION_OUT:
+ WLog_Print(urbdrc->log, WLOG_ERROR,
+ "Function urb_control_feature_request: OUT Unchecked");
+ Stream_Copy(s, out, OutputBufferSize);
+ Stream_Rewind(out, OutputBufferSize);
+ bmRequestType |= 0x00;
+ break;
+
+ case USBD_TRANSFER_DIRECTION_IN:
+ bmRequestType |= 0x80;
+ break;
+ }
+
+ switch (command)
+ {
+ case URB_SET_FEATURE:
+ bmRequest = 0x03; /* REQUEST_SET_FEATURE */
+ break;
+
+ case URB_CLEAR_FEATURE:
+ bmRequest = 0x01; /* REQUEST_CLEAR_FEATURE */
+ break;
+
+ default:
+ WLog_Print(urbdrc->log, WLOG_ERROR,
+ "urb_control_feature_request: Error Command 0x%02" PRIx8 "", command);
+ Stream_Free(out, TRUE);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!pdev->control_transfer(pdev, RequestId, 0, 0, bmRequestType, bmRequest, FeatureSelector,
+ Index, &usbd_status, &OutputBufferSize, Stream_Pointer(out), 1000))
+ {
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "feature control transfer failed");
+ Stream_Free(out, TRUE);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId,
+ usbd_status, OutputBufferSize);
+}
+
+static UINT urbdrc_process_transfer_request(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback,
+ wStream* s, UINT32 MessageId, IUDEVMAN* udevman,
+ int transferDir)
+{
+ UINT32 CbTsUrb = 0;
+ UINT16 Size = 0;
+ UINT16 URB_Function = 0;
+ UINT32 RequestId = 0;
+ UINT error = ERROR_INTERNAL_ERROR;
+ URBDRC_PLUGIN* urbdrc = NULL;
+
+ if (!callback || !s || !udevman || !pdev)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+
+ if (!urbdrc)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, CbTsUrb); /** CbTsUrb */
+ Stream_Read_UINT16(s, Size); /** size */
+ Stream_Read_UINT16(s, URB_Function);
+ Stream_Read_UINT32(s, RequestId);
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "URB %s[%" PRIu16 "]", urb_function_string(URB_Function),
+ URB_Function);
+
+ switch (URB_Function)
+ {
+ case TS_URB_SELECT_CONFIGURATION: /** 0x0000 */
+ error = urb_select_configuration(pdev, callback, s, RequestId, MessageId, udevman,
+ transferDir);
+ break;
+
+ case TS_URB_SELECT_INTERFACE: /** 0x0001 */
+ error =
+ urb_select_interface(pdev, callback, s, RequestId, MessageId, udevman, transferDir);
+ break;
+
+ case TS_URB_PIPE_REQUEST: /** 0x0002 */
+ error = urb_pipe_request(pdev, callback, s, RequestId, MessageId, udevman, transferDir,
+ PIPE_CANCEL);
+ break;
+
+ case TS_URB_TAKE_FRAME_LENGTH_CONTROL: /** 0x0003 */
+ /** This URB function is obsolete in Windows 2000
+ * and later operating systems
+ * and is not supported by Microsoft. */
+ break;
+
+ case TS_URB_RELEASE_FRAME_LENGTH_CONTROL: /** 0x0004 */
+ /** This URB function is obsolete in Windows 2000
+ * and later operating systems
+ * and is not supported by Microsoft. */
+ break;
+
+ case TS_URB_GET_FRAME_LENGTH: /** 0x0005 */
+ /** This URB function is obsolete in Windows 2000
+ * and later operating systems
+ * and is not supported by Microsoft. */
+ break;
+
+ case TS_URB_SET_FRAME_LENGTH: /** 0x0006 */
+ /** This URB function is obsolete in Windows 2000
+ * and later operating systems
+ * and is not supported by Microsoft. */
+ break;
+
+ case TS_URB_GET_CURRENT_FRAME_NUMBER: /** 0x0007 */
+ error = urb_get_current_frame_number(pdev, callback, s, RequestId, MessageId, udevman,
+ transferDir);
+ break;
+
+ case TS_URB_CONTROL_TRANSFER: /** 0x0008 */
+ error = urb_control_transfer(pdev, callback, s, RequestId, MessageId, udevman,
+ transferDir, URB_CONTROL_TRANSFER_NONEXTERNAL);
+ break;
+
+ case TS_URB_BULK_OR_INTERRUPT_TRANSFER: /** 0x0009 */
+ error = urb_bulk_or_interrupt_transfer(pdev, callback, s, RequestId, MessageId, udevman,
+ transferDir);
+ break;
+
+ case TS_URB_ISOCH_TRANSFER: /** 0x000A */
+ error =
+ urb_isoch_transfer(pdev, callback, s, RequestId, MessageId, udevman, transferDir);
+ break;
+
+ case TS_URB_GET_DESCRIPTOR_FROM_DEVICE: /** 0x000B */
+ error = urb_control_descriptor_request(pdev, callback, s, RequestId, MessageId, udevman,
+ 0x00, transferDir);
+ break;
+
+ case TS_URB_SET_DESCRIPTOR_TO_DEVICE: /** 0x000C */
+ error = urb_control_descriptor_request(pdev, callback, s, RequestId, MessageId, udevman,
+ 0x00, transferDir);
+ break;
+
+ case TS_URB_SET_FEATURE_TO_DEVICE: /** 0x000D */
+ error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman,
+ 0x00, URB_SET_FEATURE, transferDir);
+ break;
+
+ case TS_URB_SET_FEATURE_TO_INTERFACE: /** 0x000E */
+ error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman,
+ 0x01, URB_SET_FEATURE, transferDir);
+ break;
+
+ case TS_URB_SET_FEATURE_TO_ENDPOINT: /** 0x000F */
+ error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman,
+ 0x02, URB_SET_FEATURE, transferDir);
+ break;
+
+ case TS_URB_CLEAR_FEATURE_TO_DEVICE: /** 0x0010 */
+ error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman,
+ 0x00, URB_CLEAR_FEATURE, transferDir);
+ break;
+
+ case TS_URB_CLEAR_FEATURE_TO_INTERFACE: /** 0x0011 */
+ error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman,
+ 0x01, URB_CLEAR_FEATURE, transferDir);
+ break;
+
+ case TS_URB_CLEAR_FEATURE_TO_ENDPOINT: /** 0x0012 */
+ error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman,
+ 0x02, URB_CLEAR_FEATURE, transferDir);
+ break;
+
+ case TS_URB_GET_STATUS_FROM_DEVICE: /** 0x0013 */
+ error = urb_control_get_status_request(pdev, callback, s, RequestId, MessageId, udevman,
+ 0x00, transferDir);
+ break;
+
+ case TS_URB_GET_STATUS_FROM_INTERFACE: /** 0x0014 */
+ error = urb_control_get_status_request(pdev, callback, s, RequestId, MessageId, udevman,
+ 0x01, transferDir);
+ break;
+
+ case TS_URB_GET_STATUS_FROM_ENDPOINT: /** 0x0015 */
+ error = urb_control_get_status_request(pdev, callback, s, RequestId, MessageId, udevman,
+ 0x02, transferDir);
+ break;
+
+ case TS_URB_RESERVED_0X0016: /** 0x0016 */
+ break;
+
+ case TS_URB_VENDOR_DEVICE: /** 0x0017 */
+ error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId,
+ udevman, (0x02 << 5), /* vendor type */
+ 0x00, transferDir);
+ break;
+
+ case TS_URB_VENDOR_INTERFACE: /** 0x0018 */
+ error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId,
+ udevman, (0x02 << 5), /* vendor type */
+ 0x01, transferDir);
+ break;
+
+ case TS_URB_VENDOR_ENDPOINT: /** 0x0019 */
+ error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId,
+ udevman, (0x02 << 5), /* vendor type */
+ 0x02, transferDir);
+ break;
+
+ case TS_URB_CLASS_DEVICE: /** 0x001A */
+ error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId,
+ udevman, (0x01 << 5), /* class type */
+ 0x00, transferDir);
+ break;
+
+ case TS_URB_CLASS_INTERFACE: /** 0x001B */
+ error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId,
+ udevman, (0x01 << 5), /* class type */
+ 0x01, transferDir);
+ break;
+
+ case TS_URB_CLASS_ENDPOINT: /** 0x001C */
+ error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId,
+ udevman, (0x01 << 5), /* class type */
+ 0x02, transferDir);
+ break;
+
+ case TS_URB_RESERVE_0X001D: /** 0x001D */
+ break;
+
+ case TS_URB_SYNC_RESET_PIPE_AND_CLEAR_STALL: /** 0x001E */
+ error = urb_pipe_request(pdev, callback, s, RequestId, MessageId, udevman, transferDir,
+ PIPE_RESET);
+ break;
+
+ case TS_URB_CLASS_OTHER: /** 0x001F */
+ error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId,
+ udevman, (0x01 << 5), /* class type */
+ 0x03, transferDir);
+ break;
+
+ case TS_URB_VENDOR_OTHER: /** 0x0020 */
+ error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId,
+ udevman, (0x02 << 5), /* vendor type */
+ 0x03, transferDir);
+ break;
+
+ case TS_URB_GET_STATUS_FROM_OTHER: /** 0x0021 */
+ error = urb_control_get_status_request(pdev, callback, s, RequestId, MessageId, udevman,
+ 0x03, transferDir);
+ break;
+
+ case TS_URB_CLEAR_FEATURE_TO_OTHER: /** 0x0022 */
+ error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman,
+ 0x03, URB_CLEAR_FEATURE, transferDir);
+ break;
+
+ case TS_URB_SET_FEATURE_TO_OTHER: /** 0x0023 */
+ error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman,
+ 0x03, URB_SET_FEATURE, transferDir);
+ break;
+
+ case TS_URB_GET_DESCRIPTOR_FROM_ENDPOINT: /** 0x0024 */
+ error = urb_control_descriptor_request(pdev, callback, s, RequestId, MessageId, udevman,
+ 0x02, transferDir);
+ break;
+
+ case TS_URB_SET_DESCRIPTOR_TO_ENDPOINT: /** 0x0025 */
+ error = urb_control_descriptor_request(pdev, callback, s, RequestId, MessageId, udevman,
+ 0x02, transferDir);
+ break;
+
+ case TS_URB_CONTROL_GET_CONFIGURATION_REQUEST: /** 0x0026 */
+ error = urb_control_get_configuration_request(pdev, callback, s, RequestId, MessageId,
+ udevman, transferDir);
+ break;
+
+ case TS_URB_CONTROL_GET_INTERFACE_REQUEST: /** 0x0027 */
+ error = urb_control_get_interface_request(pdev, callback, s, RequestId, MessageId,
+ udevman, transferDir);
+ break;
+
+ case TS_URB_GET_DESCRIPTOR_FROM_INTERFACE: /** 0x0028 */
+ error = urb_control_descriptor_request(pdev, callback, s, RequestId, MessageId, udevman,
+ 0x01, transferDir);
+ break;
+
+ case TS_URB_SET_DESCRIPTOR_TO_INTERFACE: /** 0x0029 */
+ error = urb_control_descriptor_request(pdev, callback, s, RequestId, MessageId, udevman,
+ 0x01, transferDir);
+ break;
+
+ case TS_URB_GET_OS_FEATURE_DESCRIPTOR_REQUEST: /** 0x002A */
+ error = urb_os_feature_descriptor_request(pdev, callback, s, RequestId, MessageId,
+ udevman, transferDir);
+ break;
+
+ case TS_URB_RESERVE_0X002B: /** 0x002B */
+ case TS_URB_RESERVE_0X002C: /** 0x002C */
+ case TS_URB_RESERVE_0X002D: /** 0x002D */
+ case TS_URB_RESERVE_0X002E: /** 0x002E */
+ case TS_URB_RESERVE_0X002F: /** 0x002F */
+ break;
+
+ /** USB 2.0 calls start at 0x0030 */
+ case TS_URB_SYNC_RESET_PIPE: /** 0x0030 */
+ error = urb_pipe_request(pdev, callback, s, RequestId, MessageId, udevman, transferDir,
+ PIPE_RESET);
+ break;
+
+ case TS_URB_SYNC_CLEAR_STALL: /** 0x0031 */
+ urb_pipe_request(pdev, callback, s, RequestId, MessageId, udevman, transferDir,
+ PIPE_RESET);
+ break;
+
+ case TS_URB_CONTROL_TRANSFER_EX: /** 0x0032 */
+ error = urb_control_transfer(pdev, callback, s, RequestId, MessageId, udevman,
+ transferDir, URB_CONTROL_TRANSFER_EXTERNAL);
+ break;
+
+ default:
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "URB_Func: %" PRIx16 " is not found!",
+ URB_Function);
+ break;
+ }
+
+ if (error)
+ {
+ WLog_Print(urbdrc->log, WLOG_WARN,
+ "USB transfer request URB Function '%s' [0x%08x] failed with %08" PRIx32,
+ urb_function_string(URB_Function), URB_Function, error);
+ }
+
+ return error;
+}
+
+UINT urbdrc_process_udev_data_transfer(GENERIC_CHANNEL_CALLBACK* callback, URBDRC_PLUGIN* urbdrc,
+ IUDEVMAN* udevman, wStream* data)
+{
+ UINT32 InterfaceId = 0;
+ UINT32 MessageId = 0;
+ UINT32 FunctionId = 0;
+ IUDEVICE* pdev = NULL;
+ UINT error = ERROR_INTERNAL_ERROR;
+
+ if (!urbdrc || !data || !callback || !udevman)
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, data, 8))
+ goto fail;
+
+ Stream_Rewind_UINT32(data);
+
+ Stream_Read_UINT32(data, InterfaceId);
+ Stream_Read_UINT32(data, MessageId);
+ Stream_Read_UINT32(data, FunctionId);
+
+ pdev = udevman->get_udevice_by_UsbDevice(udevman, InterfaceId);
+
+ /* Device does not exist, ignore this request. */
+ if (pdev == NULL)
+ {
+ error = ERROR_SUCCESS;
+ goto fail;
+ }
+
+ /* Device has been removed, ignore this request. */
+ if (pdev->isChannelClosed(pdev))
+ {
+ error = ERROR_SUCCESS;
+ goto fail;
+ }
+
+ /* USB kernel driver detach!! */
+ pdev->detach_kernel_driver(pdev);
+
+ switch (FunctionId)
+ {
+ case CANCEL_REQUEST:
+ error = urbdrc_process_cancel_request(pdev, data, udevman);
+ break;
+
+ case REGISTER_REQUEST_CALLBACK:
+ error = urbdrc_process_register_request_callback(pdev, callback, data, udevman);
+ break;
+
+ case IO_CONTROL:
+ error = urbdrc_process_io_control(pdev, callback, data, MessageId, udevman);
+ break;
+
+ case INTERNAL_IO_CONTROL:
+ error = urbdrc_process_internal_io_control(pdev, callback, data, MessageId, udevman);
+ break;
+
+ case QUERY_DEVICE_TEXT:
+ error = urbdrc_process_query_device_text(pdev, callback, data, MessageId, udevman);
+ break;
+
+ case TRANSFER_IN_REQUEST:
+ error = urbdrc_process_transfer_request(pdev, callback, data, MessageId, udevman,
+ USBD_TRANSFER_DIRECTION_IN);
+ break;
+
+ case TRANSFER_OUT_REQUEST:
+ error = urbdrc_process_transfer_request(pdev, callback, data, MessageId, udevman,
+ USBD_TRANSFER_DIRECTION_OUT);
+ break;
+
+ case RETRACT_DEVICE:
+ error = urbdrc_process_retract_device_request(pdev, data, udevman);
+ break;
+
+ default:
+ WLog_Print(urbdrc->log, WLOG_WARN,
+ "urbdrc_process_udev_data_transfer:"
+ " unknown FunctionId 0x%" PRIX32 "",
+ FunctionId);
+ break;
+ }
+
+fail:
+ if (error)
+ {
+ WLog_WARN(TAG, "USB request failed with %08" PRIx32, error);
+ }
+
+ return error;
+}
diff --git a/channels/urbdrc/client/data_transfer.h b/channels/urbdrc/client/data_transfer.h
new file mode 100644
index 0000000..1d7126d
--- /dev/null
+++ b/channels/urbdrc/client/data_transfer.h
@@ -0,0 +1,36 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX USB Redirection
+ *
+ * Copyright 2012 Atrust corp.
+ * Copyright 2012 Alfred Liu <alfred.liu@atruscorp.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_URBDRC_CLIENT_DATA_TRANSFER_H
+#define FREERDP_CHANNEL_URBDRC_CLIENT_DATA_TRANSFER_H
+
+#include <winpr/pool.h>
+
+#include "urbdrc_main.h"
+
+#define DEVICE_CTX(dev) ((dev)->ctx)
+#define HANDLE_CTX(handle) (DEVICE_CTX((handle)->dev))
+#define TRANSFER_CTX(transfer) (HANDLE_CTX((transfer)->dev_handle))
+#define ITRANSFER_CTX(transfer) (TRANSFER_CTX(__USBI_TRANSFER_TO_LIBUSB_TRANSFER(transfer)))
+
+UINT urbdrc_process_udev_data_transfer(GENERIC_CHANNEL_CALLBACK* callback, URBDRC_PLUGIN* urbdrc,
+ IUDEVMAN* udevman, wStream* data);
+
+#endif /* FREERDP_CHANNEL_URBDRC_CLIENT_DATA_TRANSFER_H */
diff --git a/channels/urbdrc/client/libusb/CMakeLists.txt b/channels/urbdrc/client/libusb/CMakeLists.txt
new file mode 100644
index 0000000..4e219de
--- /dev/null
+++ b/channels/urbdrc/client/libusb/CMakeLists.txt
@@ -0,0 +1,36 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Atrust corp.
+# Copyright 2012 Alfred Liu <alfred.liu@atruscorp.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("urbdrc" "libusb" "")
+
+set(${MODULE_PREFIX}_SRCS
+ libusb_udevman.c
+ libusb_udevice.c
+ libusb_udevice.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ ${CMAKE_THREAD_LIBS_INIT}
+ ${LIBUSB_1_LIBRARIES}
+ winpr freerdp
+)
+
+include_directories(..)
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
+
diff --git a/channels/urbdrc/client/libusb/libusb_udevice.c b/channels/urbdrc/client/libusb/libusb_udevice.c
new file mode 100644
index 0000000..c226eb8
--- /dev/null
+++ b/channels/urbdrc/client/libusb/libusb_udevice.c
@@ -0,0 +1,1841 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX USB Redirection
+ *
+ * Copyright 2012 Atrust corp.
+ * Copyright 2012 Alfred Liu <alfred.liu@atruscorp.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/wtypes.h>
+#include <winpr/sysinfo.h>
+#include <winpr/collections.h>
+
+#include <errno.h>
+
+#include "libusb_udevice.h"
+#include "../common/urbdrc_types.h"
+
+#define BASIC_STATE_FUNC_DEFINED(_arg, _type) \
+ static _type udev_get_##_arg(IUDEVICE* idev) \
+ { \
+ UDEVICE* pdev = (UDEVICE*)idev; \
+ return pdev->_arg; \
+ } \
+ static void udev_set_##_arg(IUDEVICE* idev, _type _t) \
+ { \
+ UDEVICE* pdev = (UDEVICE*)idev; \
+ pdev->_arg = _t; \
+ }
+
+#define BASIC_POINT_FUNC_DEFINED(_arg, _type) \
+ static _type udev_get_p_##_arg(IUDEVICE* idev) \
+ { \
+ UDEVICE* pdev = (UDEVICE*)idev; \
+ return pdev->_arg; \
+ } \
+ static void udev_set_p_##_arg(IUDEVICE* idev, _type _t) \
+ { \
+ UDEVICE* pdev = (UDEVICE*)idev; \
+ pdev->_arg = _t; \
+ }
+
+#define BASIC_STATE_FUNC_REGISTER(_arg, _dev) \
+ _dev->iface.get_##_arg = udev_get_##_arg; \
+ _dev->iface.set_##_arg = udev_set_##_arg
+
+#if LIBUSB_API_VERSION >= 0x01000103
+#define HAVE_STREAM_ID_API 1
+#endif
+
+typedef struct
+{
+ wStream* data;
+ BOOL noack;
+ UINT32 MessageId;
+ UINT32 StartFrame;
+ UINT32 ErrorCount;
+ IUDEVICE* idev;
+ UINT32 OutputBufferSize;
+ GENERIC_CHANNEL_CALLBACK* callback;
+ t_isoch_transfer_cb cb;
+ wArrayList* queue;
+#if !defined(HAVE_STREAM_ID_API)
+ UINT32 streamID;
+#endif
+} ASYNC_TRANSFER_USER_DATA;
+
+static void request_free(void* value);
+
+static struct libusb_transfer* list_contains(wArrayList* list, UINT32 streamID)
+{
+ size_t count = 0;
+ if (!list)
+ return NULL;
+ count = ArrayList_Count(list);
+ for (size_t x = 0; x < count; x++)
+ {
+ struct libusb_transfer* transfer = ArrayList_GetItem(list, x);
+
+#if defined(HAVE_STREAM_ID_API)
+ const UINT32 currentID = libusb_transfer_get_stream_id(transfer);
+#else
+ const ASYNC_TRANSFER_USER_DATA* user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data;
+ const UINT32 currentID = user_data->streamID;
+#endif
+ if (currentID == streamID)
+ return transfer;
+ }
+ return NULL;
+}
+
+static UINT32 stream_id_from_buffer(struct libusb_transfer* transfer)
+{
+ if (!transfer)
+ return 0;
+#if defined(HAVE_STREAM_ID_API)
+ return libusb_transfer_get_stream_id(transfer);
+#else
+ ASYNC_TRANSFER_USER_DATA* user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data;
+ if (!user_data)
+ return 0;
+ return user_data->streamID;
+#endif
+}
+
+static void set_stream_id_for_buffer(struct libusb_transfer* transfer, UINT32 streamID)
+{
+#if defined(HAVE_STREAM_ID_API)
+ libusb_transfer_set_stream_id(transfer, streamID);
+#else
+ ASYNC_TRANSFER_USER_DATA* user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data;
+ if (!user_data)
+ return;
+ user_data->streamID = streamID;
+#endif
+}
+
+WINPR_ATTR_FORMAT_ARG(3, 8)
+static BOOL log_libusb_result_(wLog* log, DWORD lvl, WINPR_FORMAT_ARG const char* fmt,
+ const char* fkt, const char* file, size_t line, int error, ...)
+{
+ WINPR_UNUSED(file);
+
+ if (error < 0)
+ {
+ char buffer[8192] = { 0 };
+ va_list ap;
+ va_start(ap, error);
+ vsnprintf(buffer, sizeof(buffer), fmt, ap);
+ va_end(ap);
+
+ WLog_Print(log, lvl, "[%s:%" PRIuz "]: %s: error %s[%d]", fkt, line, buffer,
+ libusb_error_name(error), error);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+#define log_libusb_result(log, lvl, fmt, error, ...) \
+ log_libusb_result_((log), (lvl), (fmt), __func__, __FILE__, __LINE__, error, ##__VA_ARGS__)
+
+const char* usb_interface_class_to_string(uint8_t class)
+{
+ switch (class)
+ {
+ case LIBUSB_CLASS_PER_INTERFACE:
+ return "LIBUSB_CLASS_PER_INTERFACE";
+ case LIBUSB_CLASS_AUDIO:
+ return "LIBUSB_CLASS_AUDIO";
+ case LIBUSB_CLASS_COMM:
+ return "LIBUSB_CLASS_COMM";
+ case LIBUSB_CLASS_HID:
+ return "LIBUSB_CLASS_HID";
+ case LIBUSB_CLASS_PHYSICAL:
+ return "LIBUSB_CLASS_PHYSICAL";
+ case LIBUSB_CLASS_PRINTER:
+ return "LIBUSB_CLASS_PRINTER";
+ case LIBUSB_CLASS_IMAGE:
+ return "LIBUSB_CLASS_IMAGE";
+ case LIBUSB_CLASS_MASS_STORAGE:
+ return "LIBUSB_CLASS_MASS_STORAGE";
+ case LIBUSB_CLASS_HUB:
+ return "LIBUSB_CLASS_HUB";
+ case LIBUSB_CLASS_DATA:
+ return "LIBUSB_CLASS_DATA";
+ case LIBUSB_CLASS_SMART_CARD:
+ return "LIBUSB_CLASS_SMART_CARD";
+ case LIBUSB_CLASS_CONTENT_SECURITY:
+ return "LIBUSB_CLASS_CONTENT_SECURITY";
+ case LIBUSB_CLASS_VIDEO:
+ return "LIBUSB_CLASS_VIDEO";
+ case LIBUSB_CLASS_PERSONAL_HEALTHCARE:
+ return "LIBUSB_CLASS_PERSONAL_HEALTHCARE";
+ case LIBUSB_CLASS_DIAGNOSTIC_DEVICE:
+ return "LIBUSB_CLASS_DIAGNOSTIC_DEVICE";
+ case LIBUSB_CLASS_WIRELESS:
+ return "LIBUSB_CLASS_WIRELESS";
+ case LIBUSB_CLASS_APPLICATION:
+ return "LIBUSB_CLASS_APPLICATION";
+ case LIBUSB_CLASS_VENDOR_SPEC:
+ return "LIBUSB_CLASS_VENDOR_SPEC";
+ default:
+ return "UNKNOWN_DEVICE_CLASS";
+ }
+}
+
+static ASYNC_TRANSFER_USER_DATA* async_transfer_user_data_new(IUDEVICE* idev, UINT32 MessageId,
+ size_t offset, size_t BufferSize,
+ const BYTE* data, size_t packetSize,
+ BOOL NoAck, t_isoch_transfer_cb cb,
+ GENERIC_CHANNEL_CALLBACK* callback)
+{
+ ASYNC_TRANSFER_USER_DATA* user_data = NULL;
+ UDEVICE* pdev = (UDEVICE*)idev;
+
+ if (BufferSize > UINT32_MAX)
+ return NULL;
+
+ user_data = calloc(1, sizeof(ASYNC_TRANSFER_USER_DATA));
+ if (!user_data)
+ return NULL;
+
+ user_data->data = Stream_New(NULL, offset + BufferSize + packetSize);
+
+ if (!user_data->data)
+ {
+ free(user_data);
+ return NULL;
+ }
+
+ Stream_Seek(user_data->data, offset); /* Skip header offset */
+ if (data)
+ memcpy(Stream_Pointer(user_data->data), data, BufferSize);
+ else
+ user_data->OutputBufferSize = (UINT32)BufferSize;
+
+ user_data->noack = NoAck;
+ user_data->cb = cb;
+ user_data->callback = callback;
+ user_data->idev = idev;
+ user_data->MessageId = MessageId;
+
+ user_data->queue = pdev->request_queue;
+
+ return user_data;
+}
+
+static void async_transfer_user_data_free(ASYNC_TRANSFER_USER_DATA* user_data)
+{
+ if (user_data)
+ {
+ Stream_Free(user_data->data, TRUE);
+ free(user_data);
+ }
+}
+
+static void LIBUSB_CALL func_iso_callback(struct libusb_transfer* transfer)
+{
+ ASYNC_TRANSFER_USER_DATA* user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data;
+ const UINT32 streamID = stream_id_from_buffer(transfer);
+ wArrayList* list = user_data->queue;
+
+ ArrayList_Lock(list);
+ switch (transfer->status)
+ {
+ case LIBUSB_TRANSFER_COMPLETED:
+ {
+ UINT32 index = 0;
+ BYTE* dataStart = Stream_Pointer(user_data->data);
+ Stream_SetPosition(user_data->data,
+ 40); /* TS_URB_ISOCH_TRANSFER_RESULT IsoPacket offset */
+
+ for (int i = 0; i < transfer->num_iso_packets; i++)
+ {
+ const UINT32 act_len = transfer->iso_packet_desc[i].actual_length;
+ Stream_Write_UINT32(user_data->data, index);
+ Stream_Write_UINT32(user_data->data, act_len);
+ Stream_Write_UINT32(user_data->data, transfer->iso_packet_desc[i].status);
+
+ if (transfer->iso_packet_desc[i].status != USBD_STATUS_SUCCESS)
+ user_data->ErrorCount++;
+ else
+ {
+ const unsigned char* packetBuffer =
+ libusb_get_iso_packet_buffer_simple(transfer, i);
+ BYTE* data = dataStart + index;
+
+ if (data != packetBuffer)
+ memmove(data, packetBuffer, act_len);
+
+ index += act_len;
+ }
+ }
+ }
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+ case LIBUSB_TRANSFER_CANCELLED:
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+ case LIBUSB_TRANSFER_TIMED_OUT:
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+ case LIBUSB_TRANSFER_ERROR:
+ {
+ const UINT32 InterfaceId =
+ ((STREAM_ID_PROXY << 30) | user_data->idev->get_ReqCompletion(user_data->idev));
+
+ if (list_contains(list, streamID))
+ {
+ if (!user_data->noack)
+ {
+ const UINT32 RequestID = streamID & INTERFACE_ID_MASK;
+ user_data->cb(user_data->idev, user_data->callback, user_data->data,
+ InterfaceId, user_data->noack, user_data->MessageId, RequestID,
+ transfer->num_iso_packets, transfer->status,
+ user_data->StartFrame, user_data->ErrorCount,
+ user_data->OutputBufferSize);
+ user_data->data = NULL;
+ }
+ ArrayList_Remove(list, transfer);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ ArrayList_Unlock(list);
+}
+
+static const LIBUSB_ENDPOINT_DESCEIPTOR* func_get_ep_desc(LIBUSB_CONFIG_DESCRIPTOR* LibusbConfig,
+ MSUSB_CONFIG_DESCRIPTOR* MsConfig,
+ UINT32 EndpointAddress)
+{
+ MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces = MsConfig->MsInterfaces;
+ const LIBUSB_INTERFACE* interface = LibusbConfig->interface;
+
+ for (UINT32 inum = 0; inum < MsConfig->NumInterfaces; inum++)
+ {
+ BYTE alt = MsInterfaces[inum]->AlternateSetting;
+ const LIBUSB_ENDPOINT_DESCEIPTOR* endpoint = interface[inum].altsetting[alt].endpoint;
+
+ for (UINT32 pnum = 0; pnum < MsInterfaces[inum]->NumberOfPipes; pnum++)
+ {
+ if (endpoint[pnum].bEndpointAddress == EndpointAddress)
+ {
+ return &endpoint[pnum];
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static void LIBUSB_CALL func_bulk_transfer_cb(struct libusb_transfer* transfer)
+{
+ ASYNC_TRANSFER_USER_DATA* user_data = NULL;
+ uint32_t streamID = 0;
+ wArrayList* list = NULL;
+
+ user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data;
+ if (!user_data)
+ {
+ WLog_ERR(TAG, "[%s]: Invalid transfer->user_data!");
+ return;
+ }
+ list = user_data->queue;
+ ArrayList_Lock(list);
+ streamID = stream_id_from_buffer(transfer);
+
+ if (list_contains(list, streamID))
+ {
+ const UINT32 InterfaceId =
+ ((STREAM_ID_PROXY << 30) | user_data->idev->get_ReqCompletion(user_data->idev));
+ const UINT32 RequestID = streamID & INTERFACE_ID_MASK;
+
+ user_data->cb(user_data->idev, user_data->callback, user_data->data, InterfaceId,
+ user_data->noack, user_data->MessageId, RequestID, transfer->num_iso_packets,
+ transfer->status, user_data->StartFrame, user_data->ErrorCount,
+ transfer->actual_length);
+ user_data->data = NULL;
+ ArrayList_Remove(list, transfer);
+ }
+ ArrayList_Unlock(list);
+}
+
+static BOOL func_set_usbd_status(URBDRC_PLUGIN* urbdrc, UDEVICE* pdev, UINT32* status,
+ int err_result)
+{
+ if (!urbdrc || !status)
+ return FALSE;
+
+ switch (err_result)
+ {
+ case LIBUSB_SUCCESS:
+ *status = USBD_STATUS_SUCCESS;
+ break;
+
+ case LIBUSB_ERROR_IO:
+ *status = USBD_STATUS_STALL_PID;
+ break;
+
+ case LIBUSB_ERROR_INVALID_PARAM:
+ *status = USBD_STATUS_INVALID_PARAMETER;
+ break;
+
+ case LIBUSB_ERROR_ACCESS:
+ *status = USBD_STATUS_NOT_ACCESSED;
+ break;
+
+ case LIBUSB_ERROR_NO_DEVICE:
+ *status = USBD_STATUS_DEVICE_GONE;
+
+ if (pdev)
+ {
+ if (!(pdev->status & URBDRC_DEVICE_NOT_FOUND))
+ pdev->status |= URBDRC_DEVICE_NOT_FOUND;
+ }
+
+ break;
+
+ case LIBUSB_ERROR_NOT_FOUND:
+ *status = USBD_STATUS_STALL_PID;
+ break;
+
+ case LIBUSB_ERROR_BUSY:
+ *status = USBD_STATUS_STALL_PID;
+ break;
+
+ case LIBUSB_ERROR_TIMEOUT:
+ *status = USBD_STATUS_TIMEOUT;
+ break;
+
+ case LIBUSB_ERROR_OVERFLOW:
+ *status = USBD_STATUS_STALL_PID;
+ break;
+
+ case LIBUSB_ERROR_PIPE:
+ *status = USBD_STATUS_STALL_PID;
+ break;
+
+ case LIBUSB_ERROR_INTERRUPTED:
+ *status = USBD_STATUS_STALL_PID;
+ break;
+
+ case LIBUSB_ERROR_NO_MEM:
+ *status = USBD_STATUS_NO_MEMORY;
+ break;
+
+ case LIBUSB_ERROR_NOT_SUPPORTED:
+ *status = USBD_STATUS_NOT_SUPPORTED;
+ break;
+
+ case LIBUSB_ERROR_OTHER:
+ *status = USBD_STATUS_STALL_PID;
+ break;
+
+ default:
+ *status = USBD_STATUS_SUCCESS;
+ break;
+ }
+
+ return TRUE;
+}
+
+static int func_config_release_all_interface(URBDRC_PLUGIN* urbdrc,
+ LIBUSB_DEVICE_HANDLE* libusb_handle,
+ UINT32 NumInterfaces)
+{
+ for (UINT32 i = 0; i < NumInterfaces; i++)
+ {
+ int ret = libusb_release_interface(libusb_handle, i);
+
+ if (log_libusb_result(urbdrc->log, WLOG_WARN, "libusb_release_interface", ret))
+ return -1;
+ }
+
+ return 0;
+}
+
+static int func_claim_all_interface(URBDRC_PLUGIN* urbdrc, LIBUSB_DEVICE_HANDLE* libusb_handle,
+ int NumInterfaces)
+{
+ int ret = 0;
+
+ for (int i = 0; i < NumInterfaces; i++)
+ {
+ ret = libusb_claim_interface(libusb_handle, i);
+
+ if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_claim_interface", ret))
+ return -1;
+ }
+
+ return 0;
+}
+
+static LIBUSB_DEVICE* udev_get_libusb_dev(libusb_context* context, uint8_t bus_number,
+ uint8_t dev_number)
+{
+ LIBUSB_DEVICE** libusb_list = NULL;
+ LIBUSB_DEVICE* device = NULL;
+ const ssize_t total_device = libusb_get_device_list(context, &libusb_list);
+
+ for (ssize_t i = 0; i < total_device; i++)
+ {
+ LIBUSB_DEVICE* dev = libusb_list[i];
+ if ((bus_number == libusb_get_bus_number(dev)) &&
+ (dev_number == libusb_get_device_address(dev)))
+ device = dev;
+ else
+ libusb_unref_device(dev);
+ }
+
+ libusb_free_device_list(libusb_list, 0);
+ return device;
+}
+
+static LIBUSB_DEVICE_DESCRIPTOR* udev_new_descript(URBDRC_PLUGIN* urbdrc, LIBUSB_DEVICE* libusb_dev)
+{
+ int ret = 0;
+ LIBUSB_DEVICE_DESCRIPTOR* descriptor =
+ (LIBUSB_DEVICE_DESCRIPTOR*)calloc(1, sizeof(LIBUSB_DEVICE_DESCRIPTOR));
+ if (!descriptor)
+ return NULL;
+ ret = libusb_get_device_descriptor(libusb_dev, descriptor);
+
+ if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_get_device_descriptor", ret))
+ {
+ free(descriptor);
+ return NULL;
+ }
+
+ return descriptor;
+}
+
+static int libusb_udev_select_interface(IUDEVICE* idev, BYTE InterfaceNumber, BYTE AlternateSetting)
+{
+ int error = 0;
+ int diff = 0;
+ UDEVICE* pdev = (UDEVICE*)idev;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ MSUSB_CONFIG_DESCRIPTOR* MsConfig = NULL;
+ MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces = NULL;
+
+ if (!pdev || !pdev->urbdrc)
+ return -1;
+
+ urbdrc = pdev->urbdrc;
+ MsConfig = pdev->MsConfig;
+
+ if (MsConfig)
+ {
+ MsInterfaces = MsConfig->MsInterfaces;
+ if (MsInterfaces)
+ {
+ WLog_Print(urbdrc->log, WLOG_INFO,
+ "select Interface(%" PRIu8 ") curr AlternateSetting(%" PRIu8
+ ") new AlternateSetting(%" PRIu8 ")",
+ InterfaceNumber, MsInterfaces[InterfaceNumber]->AlternateSetting,
+ AlternateSetting);
+
+ if (MsInterfaces[InterfaceNumber]->AlternateSetting != AlternateSetting)
+ {
+ diff = 1;
+ }
+ }
+
+ if (diff)
+ {
+ error = libusb_set_interface_alt_setting(pdev->libusb_handle, InterfaceNumber,
+ AlternateSetting);
+
+ log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_set_interface_alt_setting", error);
+ }
+ }
+
+ return error;
+}
+
+static MSUSB_CONFIG_DESCRIPTOR*
+libusb_udev_complete_msconfig_setup(IUDEVICE* idev, MSUSB_CONFIG_DESCRIPTOR* MsConfig)
+{
+ UDEVICE* pdev = (UDEVICE*)idev;
+ MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces = NULL;
+ MSUSB_INTERFACE_DESCRIPTOR* MsInterface = NULL;
+ MSUSB_PIPE_DESCRIPTOR** MsPipes = NULL;
+ MSUSB_PIPE_DESCRIPTOR* MsPipe = NULL;
+ MSUSB_PIPE_DESCRIPTOR** t_MsPipes = NULL;
+ MSUSB_PIPE_DESCRIPTOR* t_MsPipe = NULL;
+ LIBUSB_CONFIG_DESCRIPTOR* LibusbConfig = NULL;
+ const LIBUSB_INTERFACE* LibusbInterface = NULL;
+ const LIBUSB_INTERFACE_DESCRIPTOR* LibusbAltsetting = NULL;
+ const LIBUSB_ENDPOINT_DESCEIPTOR* LibusbEndpoint = NULL;
+ BYTE LibusbNumEndpoint = 0;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ UINT32 MsOutSize = 0;
+
+ if (!pdev || !pdev->LibusbConfig || !pdev->urbdrc || !MsConfig)
+ return NULL;
+
+ urbdrc = pdev->urbdrc;
+ LibusbConfig = pdev->LibusbConfig;
+
+ if (LibusbConfig->bNumInterfaces != MsConfig->NumInterfaces)
+ {
+ WLog_Print(urbdrc->log, WLOG_ERROR,
+ "Select Configuration: Libusb NumberInterfaces(%" PRIu8 ") is different "
+ "with MsConfig NumberInterfaces(%" PRIu32 ")",
+ LibusbConfig->bNumInterfaces, MsConfig->NumInterfaces);
+ }
+
+ /* replace MsPipes for libusb */
+ MsInterfaces = MsConfig->MsInterfaces;
+
+ for (UINT32 inum = 0; inum < MsConfig->NumInterfaces; inum++)
+ {
+ MsInterface = MsInterfaces[inum];
+ /* get libusb's number of endpoints */
+ LibusbInterface = &LibusbConfig->interface[MsInterface->InterfaceNumber];
+ LibusbAltsetting = &LibusbInterface->altsetting[MsInterface->AlternateSetting];
+ LibusbNumEndpoint = LibusbAltsetting->bNumEndpoints;
+ t_MsPipes =
+ (MSUSB_PIPE_DESCRIPTOR**)calloc(LibusbNumEndpoint, sizeof(MSUSB_PIPE_DESCRIPTOR*));
+
+ for (UINT32 pnum = 0; pnum < LibusbNumEndpoint; pnum++)
+ {
+ t_MsPipe = (MSUSB_PIPE_DESCRIPTOR*)calloc(1, sizeof(MSUSB_PIPE_DESCRIPTOR));
+
+ if (pnum < MsInterface->NumberOfPipes && MsInterface->MsPipes)
+ {
+ MsPipe = MsInterface->MsPipes[pnum];
+ t_MsPipe->MaximumPacketSize = MsPipe->MaximumPacketSize;
+ t_MsPipe->MaximumTransferSize = MsPipe->MaximumTransferSize;
+ t_MsPipe->PipeFlags = MsPipe->PipeFlags;
+ }
+ else
+ {
+ t_MsPipe->MaximumPacketSize = 0;
+ t_MsPipe->MaximumTransferSize = 0xffffffff;
+ t_MsPipe->PipeFlags = 0;
+ }
+
+ t_MsPipe->PipeHandle = 0;
+ t_MsPipe->bEndpointAddress = 0;
+ t_MsPipe->bInterval = 0;
+ t_MsPipe->PipeType = 0;
+ t_MsPipe->InitCompleted = 0;
+ t_MsPipes[pnum] = t_MsPipe;
+ }
+
+ msusb_mspipes_replace(MsInterface, t_MsPipes, LibusbNumEndpoint);
+ }
+
+ /* setup configuration */
+ MsOutSize = 8;
+ /* ConfigurationHandle: 4 bytes
+ * ---------------------------------------------------------------
+ * ||<<< 1 byte >>>|<<< 1 byte >>>|<<<<<<<<<< 2 byte >>>>>>>>>>>||
+ * || bus_number | dev_number | bConfigurationValue ||
+ * ---------------------------------------------------------------
+ * ***********************/
+ MsConfig->ConfigurationHandle =
+ MsConfig->bConfigurationValue | (pdev->bus_number << 24) | (pdev->dev_number << 16);
+ MsInterfaces = MsConfig->MsInterfaces;
+
+ for (UINT32 inum = 0; inum < MsConfig->NumInterfaces; inum++)
+ {
+ MsOutSize += 16;
+ MsInterface = MsInterfaces[inum];
+ /* get libusb's interface */
+ LibusbInterface = &LibusbConfig->interface[MsInterface->InterfaceNumber];
+ LibusbAltsetting = &LibusbInterface->altsetting[MsInterface->AlternateSetting];
+ /* InterfaceHandle: 4 bytes
+ * ---------------------------------------------------------------
+ * ||<<< 1 byte >>>|<<< 1 byte >>>|<<< 1 byte >>>|<<< 1 byte >>>||
+ * || bus_number | dev_number | altsetting | interfaceNum ||
+ * ---------------------------------------------------------------
+ * ***********************/
+ MsInterface->InterfaceHandle = LibusbAltsetting->bInterfaceNumber |
+ (LibusbAltsetting->bAlternateSetting << 8) |
+ (pdev->dev_number << 16) | (pdev->bus_number << 24);
+ MsInterface->Length = 16 + (MsInterface->NumberOfPipes * 20);
+ MsInterface->bInterfaceClass = LibusbAltsetting->bInterfaceClass;
+ MsInterface->bInterfaceSubClass = LibusbAltsetting->bInterfaceSubClass;
+ MsInterface->bInterfaceProtocol = LibusbAltsetting->bInterfaceProtocol;
+ MsInterface->InitCompleted = 1;
+ MsPipes = MsInterface->MsPipes;
+ LibusbNumEndpoint = LibusbAltsetting->bNumEndpoints;
+
+ for (UINT32 pnum = 0; pnum < LibusbNumEndpoint; pnum++)
+ {
+ MsOutSize += 20;
+ MsPipe = MsPipes[pnum];
+ /* get libusb's endpoint */
+ LibusbEndpoint = &LibusbAltsetting->endpoint[pnum];
+ /* PipeHandle: 4 bytes
+ * ---------------------------------------------------------------
+ * ||<<< 1 byte >>>|<<< 1 byte >>>|<<<<<<<<<< 2 byte >>>>>>>>>>>||
+ * || bus_number | dev_number | bEndpointAddress ||
+ * ---------------------------------------------------------------
+ * ***********************/
+ MsPipe->PipeHandle = LibusbEndpoint->bEndpointAddress | (pdev->dev_number << 16) |
+ (pdev->bus_number << 24);
+ /* count endpoint max packet size */
+ int max = LibusbEndpoint->wMaxPacketSize & 0x07ff;
+ BYTE attr = LibusbEndpoint->bmAttributes;
+
+ if ((attr & 0x3) == 1 || (attr & 0x3) == 3)
+ {
+ max *= (1 + ((LibusbEndpoint->wMaxPacketSize >> 11) & 3));
+ }
+
+ MsPipe->MaximumPacketSize = max;
+ MsPipe->bEndpointAddress = LibusbEndpoint->bEndpointAddress;
+ MsPipe->bInterval = LibusbEndpoint->bInterval;
+ MsPipe->PipeType = attr & 0x3;
+ MsPipe->InitCompleted = 1;
+ }
+ }
+
+ MsConfig->MsOutSize = MsOutSize;
+ MsConfig->InitCompleted = 1;
+
+ /* replace device's MsConfig */
+ if (MsConfig != pdev->MsConfig)
+ {
+ msusb_msconfig_free(pdev->MsConfig);
+ pdev->MsConfig = MsConfig;
+ }
+
+ return MsConfig;
+}
+
+static int libusb_udev_select_configuration(IUDEVICE* idev, UINT32 bConfigurationValue)
+{
+ UDEVICE* pdev = (UDEVICE*)idev;
+ MSUSB_CONFIG_DESCRIPTOR* MsConfig = NULL;
+ LIBUSB_DEVICE_HANDLE* libusb_handle = NULL;
+ LIBUSB_DEVICE* libusb_dev = NULL;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ LIBUSB_CONFIG_DESCRIPTOR** LibusbConfig = NULL;
+ int ret = 0;
+
+ if (!pdev || !pdev->MsConfig || !pdev->LibusbConfig || !pdev->urbdrc)
+ return -1;
+
+ urbdrc = pdev->urbdrc;
+ MsConfig = pdev->MsConfig;
+ libusb_handle = pdev->libusb_handle;
+ libusb_dev = pdev->libusb_dev;
+ LibusbConfig = &pdev->LibusbConfig;
+
+ if (MsConfig->InitCompleted)
+ {
+ func_config_release_all_interface(pdev->urbdrc, libusb_handle,
+ (*LibusbConfig)->bNumInterfaces);
+ }
+
+ /* The configuration value -1 is mean to put the device in unconfigured state. */
+ if (bConfigurationValue == 0)
+ ret = libusb_set_configuration(libusb_handle, -1);
+ else
+ ret = libusb_set_configuration(libusb_handle, bConfigurationValue);
+
+ if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_set_configuration", ret))
+ {
+ func_claim_all_interface(urbdrc, libusb_handle, (*LibusbConfig)->bNumInterfaces);
+ return -1;
+ }
+ else
+ {
+ ret = libusb_get_active_config_descriptor(libusb_dev, LibusbConfig);
+
+ if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_set_configuration", ret))
+ {
+ func_claim_all_interface(urbdrc, libusb_handle, (*LibusbConfig)->bNumInterfaces);
+ return -1;
+ }
+ }
+
+ func_claim_all_interface(urbdrc, libusb_handle, (*LibusbConfig)->bNumInterfaces);
+ return 0;
+}
+
+static int libusb_udev_control_pipe_request(IUDEVICE* idev, UINT32 RequestId,
+ UINT32 EndpointAddress, UINT32* UsbdStatus, int command)
+{
+ int error = 0;
+ UDEVICE* pdev = (UDEVICE*)idev;
+
+ /*
+ pdev->request_queue->register_request(pdev->request_queue, RequestId, NULL, 0);
+ */
+ switch (command)
+ {
+ case PIPE_CANCEL:
+ /** cancel bulk or int transfer */
+ idev->cancel_all_transfer_request(idev);
+ // dummy_wait_s_obj(1);
+ /** set feature to ep (set halt)*/
+ error = libusb_control_transfer(
+ pdev->libusb_handle, LIBUSB_ENDPOINT_OUT | LIBUSB_RECIPIENT_ENDPOINT,
+ LIBUSB_REQUEST_SET_FEATURE, ENDPOINT_HALT, EndpointAddress, NULL, 0, 1000);
+ break;
+
+ case PIPE_RESET:
+ idev->cancel_all_transfer_request(idev);
+ error = libusb_clear_halt(pdev->libusb_handle, EndpointAddress);
+ // func_set_usbd_status(pdev, UsbdStatus, error);
+ break;
+
+ default:
+ error = -0xff;
+ break;
+ }
+
+ *UsbdStatus = 0;
+ return error;
+}
+
+static UINT32 libusb_udev_control_query_device_text(IUDEVICE* idev, UINT32 TextType,
+ UINT16 LocaleId, UINT8* BufferSize,
+ BYTE* Buffer)
+{
+ UDEVICE* pdev = (UDEVICE*)idev;
+ LIBUSB_DEVICE_DESCRIPTOR* devDescriptor = NULL;
+ const char strDesc[] = "Generic Usb String";
+ char deviceLocation[25] = { 0 };
+ BYTE bus_number = 0;
+ BYTE device_address = 0;
+ int ret = 0;
+ size_t len = 0;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ WCHAR* text = (WCHAR*)Buffer;
+ BYTE slen = 0;
+ BYTE locale = 0;
+ const UINT8 inSize = *BufferSize;
+
+ *BufferSize = 0;
+ if (!pdev || !pdev->devDescriptor || !pdev->urbdrc)
+ return ERROR_INVALID_DATA;
+
+ urbdrc = pdev->urbdrc;
+ devDescriptor = pdev->devDescriptor;
+
+ switch (TextType)
+ {
+ case DeviceTextDescription:
+ {
+ BYTE data[0x100] = { 0 };
+ ret = libusb_get_string_descriptor(pdev->libusb_handle, devDescriptor->iProduct,
+ LocaleId, data, 0xFF);
+ /* The returned data in the buffer is:
+ * 1 byte length of following data
+ * 1 byte descriptor type, must be 0x03 for strings
+ * n WCHAR unicode string (of length / 2 characters) including '\0'
+ */
+ slen = data[0];
+ locale = data[1];
+
+ if ((ret <= 0) || (ret <= 4) || (slen <= 4) || (locale != LIBUSB_DT_STRING) ||
+ (ret > UINT8_MAX))
+ {
+ const char* msg = "SHORT_DESCRIPTOR";
+ if (ret < 0)
+ msg = libusb_error_name(ret);
+ WLog_Print(urbdrc->log, WLOG_DEBUG,
+ "libusb_get_string_descriptor: "
+ "%s [%d], iProduct: %" PRIu8 "!",
+ msg, ret, devDescriptor->iProduct);
+
+ len = MIN(sizeof(strDesc), inSize);
+ for (ssize_t i = 0; i < len; i++)
+ text[i] = (WCHAR)strDesc[i];
+
+ *BufferSize = (BYTE)(len * 2);
+ }
+ else
+ {
+ /* ret and slen should be equals, but you never know creativity
+ * of device manufacturers...
+ * So also check the string length returned as server side does
+ * not honor strings with multi '\0' characters well.
+ */
+ const size_t rchar = _wcsnlen((WCHAR*)&data[2], sizeof(data) / 2);
+ len = MIN((BYTE)ret, slen);
+ len = MIN(len, inSize);
+ len = MIN(len, rchar * 2 + sizeof(WCHAR));
+ memcpy(Buffer, &data[2], len);
+
+ /* Just as above, the returned WCHAR string should be '\0'
+ * terminated, but never trust hardware to conform to specs... */
+ Buffer[len - 2] = '\0';
+ Buffer[len - 1] = '\0';
+ *BufferSize = (BYTE)len;
+ }
+ }
+ break;
+
+ case DeviceTextLocationInformation:
+ bus_number = libusb_get_bus_number(pdev->libusb_dev);
+ device_address = libusb_get_device_address(pdev->libusb_dev);
+ sprintf_s(deviceLocation, sizeof(deviceLocation),
+ "Port_#%04" PRIu8 ".Hub_#%04" PRIu8 "", device_address, bus_number);
+
+ len = strnlen(deviceLocation,
+ MIN(sizeof(deviceLocation), (inSize > 0) ? inSize - 1U : 0));
+ for (ssize_t i = 0; i < len; i++)
+ text[i] = (WCHAR)deviceLocation[i];
+ text[len++] = '\0';
+ *BufferSize = (UINT8)(len * sizeof(WCHAR));
+ break;
+
+ default:
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "Query Text: unknown TextType %" PRIu32 "",
+ TextType);
+ return ERROR_INVALID_DATA;
+ }
+
+ return S_OK;
+}
+
+static int libusb_udev_os_feature_descriptor_request(IUDEVICE* idev, UINT32 RequestId,
+ BYTE Recipient, BYTE InterfaceNumber,
+ BYTE Ms_PageIndex, UINT16 Ms_featureDescIndex,
+ UINT32* UsbdStatus, UINT32* BufferSize,
+ BYTE* Buffer, UINT32 Timeout)
+{
+ UDEVICE* pdev = (UDEVICE*)idev;
+ BYTE ms_string_desc[0x13] = { 0 };
+ int error = 0;
+
+ WINPR_ASSERT(idev);
+ WINPR_ASSERT(UsbdStatus);
+ WINPR_ASSERT(BufferSize);
+ WINPR_ASSERT(*BufferSize <= UINT16_MAX);
+
+ /*
+ pdev->request_queue->register_request(pdev->request_queue, RequestId, NULL, 0);
+ */
+ error = libusb_control_transfer(pdev->libusb_handle, LIBUSB_ENDPOINT_IN | Recipient,
+ LIBUSB_REQUEST_GET_DESCRIPTOR, 0x03ee, 0, ms_string_desc, 0x12,
+ Timeout);
+
+ log_libusb_result(pdev->urbdrc->log, WLOG_DEBUG, "libusb_control_transfer", error);
+
+ if (error > 0)
+ {
+ const BYTE bMS_Vendorcode = ms_string_desc[16];
+ /** get os descriptor */
+ error = libusb_control_transfer(
+ pdev->libusb_handle, LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | Recipient,
+ bMS_Vendorcode, (UINT16)((InterfaceNumber << 8) | Ms_PageIndex), Ms_featureDescIndex,
+ Buffer, (UINT16)*BufferSize, Timeout);
+ log_libusb_result(pdev->urbdrc->log, WLOG_DEBUG, "libusb_control_transfer", error);
+
+ if (error >= 0)
+ *BufferSize = (UINT32)error;
+ }
+
+ if (error < 0)
+ *UsbdStatus = USBD_STATUS_STALL_PID;
+ else
+ *UsbdStatus = USBD_STATUS_SUCCESS;
+
+ return ERROR_SUCCESS;
+}
+
+static int libusb_udev_query_device_descriptor(IUDEVICE* idev, int offset)
+{
+ UDEVICE* pdev = (UDEVICE*)idev;
+
+ switch (offset)
+ {
+ case B_LENGTH:
+ return pdev->devDescriptor->bLength;
+
+ case B_DESCRIPTOR_TYPE:
+ return pdev->devDescriptor->bDescriptorType;
+
+ case BCD_USB:
+ return pdev->devDescriptor->bcdUSB;
+
+ case B_DEVICE_CLASS:
+ return pdev->devDescriptor->bDeviceClass;
+
+ case B_DEVICE_SUBCLASS:
+ return pdev->devDescriptor->bDeviceSubClass;
+
+ case B_DEVICE_PROTOCOL:
+ return pdev->devDescriptor->bDeviceProtocol;
+
+ case B_MAX_PACKET_SIZE0:
+ return pdev->devDescriptor->bMaxPacketSize0;
+
+ case ID_VENDOR:
+ return pdev->devDescriptor->idVendor;
+
+ case ID_PRODUCT:
+ return pdev->devDescriptor->idProduct;
+
+ case BCD_DEVICE:
+ return pdev->devDescriptor->bcdDevice;
+
+ case I_MANUFACTURER:
+ return pdev->devDescriptor->iManufacturer;
+
+ case I_PRODUCT:
+ return pdev->devDescriptor->iProduct;
+
+ case I_SERIAL_NUMBER:
+ return pdev->devDescriptor->iSerialNumber;
+
+ case B_NUM_CONFIGURATIONS:
+ return pdev->devDescriptor->bNumConfigurations;
+
+ default:
+ return 0;
+ }
+}
+
+static BOOL libusb_udev_detach_kernel_driver(IUDEVICE* idev)
+{
+ int err = 0;
+ UDEVICE* pdev = (UDEVICE*)idev;
+ URBDRC_PLUGIN* urbdrc = NULL;
+
+ if (!pdev || !pdev->LibusbConfig || !pdev->libusb_handle || !pdev->urbdrc)
+ return FALSE;
+
+#ifdef _WIN32
+ return TRUE;
+#else
+ urbdrc = pdev->urbdrc;
+
+ if ((pdev->status & URBDRC_DEVICE_DETACH_KERNEL) == 0)
+ {
+ for (int i = 0; i < pdev->LibusbConfig->bNumInterfaces; i++)
+ {
+ err = libusb_kernel_driver_active(pdev->libusb_handle, i);
+ log_libusb_result(urbdrc->log, WLOG_DEBUG, "libusb_kernel_driver_active", err);
+
+ if (err)
+ {
+ err = libusb_detach_kernel_driver(pdev->libusb_handle, i);
+ log_libusb_result(urbdrc->log, WLOG_DEBUG, "libusb_detach_kernel_driver", err);
+ }
+ }
+
+ pdev->status |= URBDRC_DEVICE_DETACH_KERNEL;
+ }
+
+ return TRUE;
+#endif
+}
+
+static BOOL libusb_udev_attach_kernel_driver(IUDEVICE* idev)
+{
+ int err = 0;
+ UDEVICE* pdev = (UDEVICE*)idev;
+
+ if (!pdev || !pdev->LibusbConfig || !pdev->libusb_handle || !pdev->urbdrc)
+ return FALSE;
+
+ for (int i = 0; i < pdev->LibusbConfig->bNumInterfaces && err != LIBUSB_ERROR_NO_DEVICE; i++)
+ {
+ err = libusb_release_interface(pdev->libusb_handle, i);
+
+ log_libusb_result(pdev->urbdrc->log, WLOG_DEBUG, "libusb_release_interface", err);
+
+#ifndef _WIN32
+ if (err != LIBUSB_ERROR_NO_DEVICE)
+ {
+ err = libusb_attach_kernel_driver(pdev->libusb_handle, i);
+ log_libusb_result(pdev->urbdrc->log, WLOG_DEBUG, "libusb_attach_kernel_driver if=%d",
+ err, i);
+ }
+#endif
+ }
+
+ return TRUE;
+}
+
+static int libusb_udev_is_composite_device(IUDEVICE* idev)
+{
+ UDEVICE* pdev = (UDEVICE*)idev;
+ return pdev->isCompositeDevice;
+}
+
+static int libusb_udev_is_exist(IUDEVICE* idev)
+{
+ UDEVICE* pdev = (UDEVICE*)idev;
+ return (pdev->status & URBDRC_DEVICE_NOT_FOUND) ? 0 : 1;
+}
+
+static int libusb_udev_is_channel_closed(IUDEVICE* idev)
+{
+ UDEVICE* pdev = (UDEVICE*)idev;
+ IUDEVMAN* udevman = NULL;
+ if (!pdev || !pdev->urbdrc)
+ return 1;
+
+ udevman = pdev->urbdrc->udevman;
+ if (udevman)
+ {
+ if (udevman->status & URBDRC_DEVICE_CHANNEL_CLOSED)
+ return 1;
+ }
+
+ if (pdev->status & URBDRC_DEVICE_CHANNEL_CLOSED)
+ return 1;
+
+ return 0;
+}
+
+static int libusb_udev_is_already_send(IUDEVICE* idev)
+{
+ UDEVICE* pdev = (UDEVICE*)idev;
+ return (pdev->status & URBDRC_DEVICE_ALREADY_SEND) ? 1 : 0;
+}
+
+/* This is called from channel cleanup code.
+ * Avoid double free, just remove the device and mark the channel closed. */
+static void libusb_udev_mark_channel_closed(IUDEVICE* idev)
+{
+ UDEVICE* pdev = (UDEVICE*)idev;
+ if (pdev && ((pdev->status & URBDRC_DEVICE_CHANNEL_CLOSED) == 0))
+ {
+ URBDRC_PLUGIN* urbdrc = pdev->urbdrc;
+ const uint8_t busNr = idev->get_bus_number(idev);
+ const uint8_t devNr = idev->get_dev_number(idev);
+
+ pdev->status |= URBDRC_DEVICE_CHANNEL_CLOSED;
+ urbdrc->udevman->unregister_udevice(urbdrc->udevman, busNr, devNr);
+ }
+}
+
+/* This is called by local events where the device is removed or in an error
+ * state. Remove the device from redirection and close the channel. */
+static void libusb_udev_channel_closed(IUDEVICE* idev)
+{
+ UDEVICE* pdev = (UDEVICE*)idev;
+ if (pdev && ((pdev->status & URBDRC_DEVICE_CHANNEL_CLOSED) == 0))
+ {
+ URBDRC_PLUGIN* urbdrc = pdev->urbdrc;
+ const uint8_t busNr = idev->get_bus_number(idev);
+ const uint8_t devNr = idev->get_dev_number(idev);
+ IWTSVirtualChannel* channel = NULL;
+
+ if (pdev->channelManager)
+ channel = IFCALLRESULT(NULL, pdev->channelManager->FindChannelById,
+ pdev->channelManager, pdev->channelID);
+
+ pdev->status |= URBDRC_DEVICE_CHANNEL_CLOSED;
+
+ if (channel)
+ channel->Write(channel, 0, NULL, NULL);
+
+ urbdrc->udevman->unregister_udevice(urbdrc->udevman, busNr, devNr);
+ }
+}
+
+static void libusb_udev_set_already_send(IUDEVICE* idev)
+{
+ UDEVICE* pdev = (UDEVICE*)idev;
+ pdev->status |= URBDRC_DEVICE_ALREADY_SEND;
+}
+
+static char* libusb_udev_get_path(IUDEVICE* idev)
+{
+ UDEVICE* pdev = (UDEVICE*)idev;
+ return pdev->path;
+}
+
+static int libusb_udev_query_device_port_status(IUDEVICE* idev, UINT32* UsbdStatus,
+ UINT32* BufferSize, BYTE* Buffer)
+{
+ UDEVICE* pdev = (UDEVICE*)idev;
+ int success = 0;
+ int ret = 0;
+ URBDRC_PLUGIN* urbdrc = NULL;
+
+ if (!pdev || !pdev->urbdrc)
+ return -1;
+
+ urbdrc = pdev->urbdrc;
+
+ if (pdev->hub_handle != NULL)
+ {
+ ret = idev->control_transfer(
+ idev, 0xffff, 0, 0,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_OTHER,
+ LIBUSB_REQUEST_GET_STATUS, 0, pdev->port_number, UsbdStatus, BufferSize, Buffer, 1000);
+
+ if (log_libusb_result(urbdrc->log, WLOG_DEBUG, "libusb_control_transfer", ret))
+ *BufferSize = 0;
+ else
+ {
+ WLog_Print(urbdrc->log, WLOG_DEBUG,
+ "PORT STATUS:0x%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "", Buffer[3],
+ Buffer[2], Buffer[1], Buffer[0]);
+ success = 1;
+ }
+ }
+
+ return success;
+}
+
+static int libusb_udev_isoch_transfer(IUDEVICE* idev, GENERIC_CHANNEL_CALLBACK* callback,
+ UINT32 MessageId, UINT32 RequestId, UINT32 EndpointAddress,
+ UINT32 TransferFlags, UINT32 StartFrame, UINT32 ErrorCount,
+ BOOL NoAck, const BYTE* packetDescriptorData,
+ UINT32 NumberOfPackets, UINT32 BufferSize, const BYTE* Buffer,
+ t_isoch_transfer_cb cb, UINT32 Timeout)
+{
+ int rc = 0;
+ UINT32 iso_packet_size = 0;
+ UDEVICE* pdev = (UDEVICE*)idev;
+ ASYNC_TRANSFER_USER_DATA* user_data = NULL;
+ struct libusb_transfer* iso_transfer = NULL;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ size_t outSize = (NumberOfPackets * 12);
+ uint32_t streamID = 0x40000000 | RequestId;
+
+ if (!pdev || !pdev->urbdrc)
+ return -1;
+
+ urbdrc = pdev->urbdrc;
+ user_data = async_transfer_user_data_new(idev, MessageId, 48, BufferSize, Buffer,
+ outSize + 1024, NoAck, cb, callback);
+
+ if (!user_data)
+ return -1;
+
+ user_data->ErrorCount = ErrorCount;
+ user_data->StartFrame = StartFrame;
+
+ if (!Buffer)
+ Stream_Seek(user_data->data, (NumberOfPackets * 12));
+
+ if (NumberOfPackets > 0)
+ {
+ iso_packet_size = BufferSize / NumberOfPackets;
+ iso_transfer = libusb_alloc_transfer((int)NumberOfPackets);
+ }
+
+ if (iso_transfer == NULL)
+ {
+ WLog_Print(urbdrc->log, WLOG_ERROR,
+ "Error: libusb_alloc_transfer [NumberOfPackets=%" PRIu32 ", BufferSize=%" PRIu32
+ " ]",
+ NumberOfPackets, BufferSize);
+ async_transfer_user_data_free(user_data);
+ return -1;
+ }
+
+ /** process URB_FUNCTION_IOSCH_TRANSFER */
+ libusb_fill_iso_transfer(iso_transfer, pdev->libusb_handle, EndpointAddress,
+ Stream_Pointer(user_data->data), BufferSize, NumberOfPackets,
+ func_iso_callback, user_data, Timeout);
+ set_stream_id_for_buffer(iso_transfer, streamID);
+ libusb_set_iso_packet_lengths(iso_transfer, iso_packet_size);
+
+ if (!ArrayList_Append(pdev->request_queue, iso_transfer))
+ {
+ WLog_Print(urbdrc->log, WLOG_WARN,
+ "Failed to queue iso transfer, streamID %08" PRIx32 " already in use!",
+ streamID);
+ request_free(iso_transfer);
+ return -1;
+ }
+ rc = libusb_submit_transfer(iso_transfer);
+ if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_submit_transfer", rc))
+ return -1;
+ return rc;
+}
+
+static BOOL libusb_udev_control_transfer(IUDEVICE* idev, UINT32 RequestId, UINT32 EndpointAddress,
+ UINT32 TransferFlags, BYTE bmRequestType, BYTE Request,
+ UINT16 Value, UINT16 Index, UINT32* UrbdStatus,
+ UINT32* BufferSize, BYTE* Buffer, UINT32 Timeout)
+{
+ int status = 0;
+ UDEVICE* pdev = (UDEVICE*)idev;
+
+ WINPR_ASSERT(BufferSize);
+ WINPR_ASSERT(*BufferSize <= UINT16_MAX);
+
+ if (!pdev || !pdev->urbdrc)
+ return FALSE;
+
+ status = libusb_control_transfer(pdev->libusb_handle, bmRequestType, Request, Value, Index,
+ Buffer, (UINT16)*BufferSize, Timeout);
+
+ if (status >= 0)
+ *BufferSize = (UINT32)status;
+ else
+ log_libusb_result(pdev->urbdrc->log, WLOG_ERROR, "libusb_control_transfer", status);
+
+ if (!func_set_usbd_status(pdev->urbdrc, pdev, UrbdStatus, status))
+ return FALSE;
+
+ return TRUE;
+}
+
+static int libusb_udev_bulk_or_interrupt_transfer(IUDEVICE* idev,
+ GENERIC_CHANNEL_CALLBACK* callback,
+ UINT32 MessageId, UINT32 RequestId,
+ UINT32 EndpointAddress, UINT32 TransferFlags,
+ BOOL NoAck, UINT32 BufferSize, const BYTE* data,
+ t_isoch_transfer_cb cb, UINT32 Timeout)
+{
+ int rc = 0;
+ UINT32 transfer_type = 0;
+ UDEVICE* pdev = (UDEVICE*)idev;
+ const LIBUSB_ENDPOINT_DESCEIPTOR* ep_desc = NULL;
+ struct libusb_transfer* transfer = NULL;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ ASYNC_TRANSFER_USER_DATA* user_data = NULL;
+ uint32_t streamID = 0x80000000 | RequestId;
+
+ if (!pdev || !pdev->LibusbConfig || !pdev->urbdrc)
+ return -1;
+
+ urbdrc = pdev->urbdrc;
+ user_data =
+ async_transfer_user_data_new(idev, MessageId, 36, BufferSize, data, 0, NoAck, cb, callback);
+
+ if (!user_data)
+ return -1;
+
+ /* alloc memory for urb transfer */
+ transfer = libusb_alloc_transfer(0);
+ if (!transfer)
+ {
+ async_transfer_user_data_free(user_data);
+ return -1;
+ }
+ transfer->user_data = user_data;
+
+ ep_desc = func_get_ep_desc(pdev->LibusbConfig, pdev->MsConfig, EndpointAddress);
+
+ if (!ep_desc)
+ {
+ WLog_Print(urbdrc->log, WLOG_ERROR, "func_get_ep_desc: endpoint 0x%" PRIx32 " not found",
+ EndpointAddress);
+ request_free(transfer);
+ return -1;
+ }
+
+ transfer_type = (ep_desc->bmAttributes) & 0x3;
+ WLog_Print(urbdrc->log, WLOG_DEBUG,
+ "urb_bulk_or_interrupt_transfer: ep:0x%" PRIx32 " "
+ "transfer_type %" PRIu32 " flag:%" PRIu32 " OutputBufferSize:0x%" PRIx32 "",
+ EndpointAddress, transfer_type, TransferFlags, BufferSize);
+
+ switch (transfer_type)
+ {
+ case BULK_TRANSFER:
+ /** Bulk Transfer */
+ libusb_fill_bulk_transfer(transfer, pdev->libusb_handle, EndpointAddress,
+ Stream_Pointer(user_data->data), BufferSize,
+ func_bulk_transfer_cb, user_data, Timeout);
+ break;
+
+ case INTERRUPT_TRANSFER:
+ /** Interrupt Transfer */
+ libusb_fill_interrupt_transfer(transfer, pdev->libusb_handle, EndpointAddress,
+ Stream_Pointer(user_data->data), BufferSize,
+ func_bulk_transfer_cb, user_data, Timeout);
+ break;
+
+ default:
+ WLog_Print(urbdrc->log, WLOG_DEBUG,
+ "urb_bulk_or_interrupt_transfer:"
+ " other transfer type 0x%" PRIX32 "",
+ transfer_type);
+ request_free(transfer);
+ return -1;
+ }
+
+ set_stream_id_for_buffer(transfer, streamID);
+
+ if (!ArrayList_Append(pdev->request_queue, transfer))
+ {
+ WLog_Print(urbdrc->log, WLOG_WARN,
+ "Failed to queue transfer, streamID %08" PRIx32 " already in use!", streamID);
+ request_free(transfer);
+ return -1;
+ }
+ rc = libusb_submit_transfer(transfer);
+ if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_submit_transfer", rc))
+ return -1;
+ return rc;
+}
+
+static int func_cancel_xact_request(URBDRC_PLUGIN* urbdrc, struct libusb_transfer* transfer)
+{
+ int status = 0;
+
+ if (!urbdrc || !transfer)
+ return -1;
+
+ status = libusb_cancel_transfer(transfer);
+
+ if (log_libusb_result(urbdrc->log, WLOG_WARN, "libusb_cancel_transfer", status))
+ {
+ if (status == LIBUSB_ERROR_NOT_FOUND)
+ return -1;
+ }
+ else
+ return 1;
+
+ return 0;
+}
+
+static void libusb_udev_cancel_all_transfer_request(IUDEVICE* idev)
+{
+ UDEVICE* pdev = (UDEVICE*)idev;
+ size_t count = 0;
+
+ if (!pdev || !pdev->request_queue || !pdev->urbdrc)
+ return;
+
+ ArrayList_Lock(pdev->request_queue);
+ count = ArrayList_Count(pdev->request_queue);
+
+ for (size_t x = 0; x < count; x++)
+ {
+ struct libusb_transfer* transfer = ArrayList_GetItem(pdev->request_queue, x);
+ func_cancel_xact_request(pdev->urbdrc, transfer);
+ }
+
+ ArrayList_Unlock(pdev->request_queue);
+}
+
+static int libusb_udev_cancel_transfer_request(IUDEVICE* idev, UINT32 RequestId)
+{
+ int rc = -1;
+ UDEVICE* pdev = (UDEVICE*)idev;
+ struct libusb_transfer* transfer = NULL;
+ uint32_t cancelID1 = 0x40000000 | RequestId;
+ uint32_t cancelID2 = 0x80000000 | RequestId;
+
+ if (!idev || !pdev->urbdrc || !pdev->request_queue)
+ return -1;
+
+ ArrayList_Lock(pdev->request_queue);
+ transfer = list_contains(pdev->request_queue, cancelID1);
+ if (!transfer)
+ transfer = list_contains(pdev->request_queue, cancelID2);
+
+ if (transfer)
+ {
+ URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)pdev->urbdrc;
+
+ rc = func_cancel_xact_request(urbdrc, transfer);
+ }
+ ArrayList_Unlock(pdev->request_queue);
+ return rc;
+}
+
+BASIC_STATE_FUNC_DEFINED(channelManager, IWTSVirtualChannelManager*)
+BASIC_STATE_FUNC_DEFINED(channelID, UINT32)
+BASIC_STATE_FUNC_DEFINED(ReqCompletion, UINT32)
+BASIC_STATE_FUNC_DEFINED(bus_number, BYTE)
+BASIC_STATE_FUNC_DEFINED(dev_number, BYTE)
+BASIC_STATE_FUNC_DEFINED(port_number, int)
+BASIC_STATE_FUNC_DEFINED(MsConfig, MSUSB_CONFIG_DESCRIPTOR*)
+
+BASIC_POINT_FUNC_DEFINED(udev, void*)
+BASIC_POINT_FUNC_DEFINED(prev, void*)
+BASIC_POINT_FUNC_DEFINED(next, void*)
+
+static UINT32 udev_get_UsbDevice(IUDEVICE* idev)
+{
+ UDEVICE* pdev = (UDEVICE*)idev;
+
+ if (!pdev)
+ return 0;
+
+ return pdev->UsbDevice;
+}
+
+static void udev_set_UsbDevice(IUDEVICE* idev, UINT32 val)
+{
+ UDEVICE* pdev = (UDEVICE*)idev;
+
+ if (!pdev)
+ return;
+
+ pdev->UsbDevice = val;
+}
+
+static void udev_free(IUDEVICE* idev)
+{
+ int rc = 0;
+ UDEVICE* udev = (UDEVICE*)idev;
+ URBDRC_PLUGIN* urbdrc = NULL;
+
+ if (!idev || !udev->urbdrc)
+ return;
+
+ urbdrc = udev->urbdrc;
+
+ libusb_udev_cancel_all_transfer_request(&udev->iface);
+ if (udev->libusb_handle)
+ {
+ rc = libusb_reset_device(udev->libusb_handle);
+
+ log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_reset_device", rc);
+ }
+
+ /* HACK: We need to wait until the cancel transfer has been processed by
+ * poll_libusb_events
+ */
+ Sleep(100);
+
+ /* release all interface and attach kernel driver */
+ udev->iface.attach_kernel_driver(idev);
+ ArrayList_Free(udev->request_queue);
+ /* free the config descriptor that send from windows */
+ msusb_msconfig_free(udev->MsConfig);
+ libusb_unref_device(udev->libusb_dev);
+ libusb_close(udev->libusb_handle);
+ libusb_close(udev->hub_handle);
+ free(udev->devDescriptor);
+ free(idev);
+}
+
+static void udev_load_interface(UDEVICE* pdev)
+{
+ WINPR_ASSERT(pdev);
+
+ /* load interface */
+ /* Basic */
+ BASIC_STATE_FUNC_REGISTER(channelManager, pdev);
+ BASIC_STATE_FUNC_REGISTER(channelID, pdev);
+ BASIC_STATE_FUNC_REGISTER(UsbDevice, pdev);
+ BASIC_STATE_FUNC_REGISTER(ReqCompletion, pdev);
+ BASIC_STATE_FUNC_REGISTER(bus_number, pdev);
+ BASIC_STATE_FUNC_REGISTER(dev_number, pdev);
+ BASIC_STATE_FUNC_REGISTER(port_number, pdev);
+ BASIC_STATE_FUNC_REGISTER(MsConfig, pdev);
+ BASIC_STATE_FUNC_REGISTER(p_udev, pdev);
+ BASIC_STATE_FUNC_REGISTER(p_prev, pdev);
+ BASIC_STATE_FUNC_REGISTER(p_next, pdev);
+ pdev->iface.isCompositeDevice = libusb_udev_is_composite_device;
+ pdev->iface.isExist = libusb_udev_is_exist;
+ pdev->iface.isAlreadySend = libusb_udev_is_already_send;
+ pdev->iface.isChannelClosed = libusb_udev_is_channel_closed;
+ pdev->iface.setAlreadySend = libusb_udev_set_already_send;
+ pdev->iface.setChannelClosed = libusb_udev_channel_closed;
+ pdev->iface.markChannelClosed = libusb_udev_mark_channel_closed;
+ pdev->iface.getPath = libusb_udev_get_path;
+ /* Transfer */
+ pdev->iface.isoch_transfer = libusb_udev_isoch_transfer;
+ pdev->iface.control_transfer = libusb_udev_control_transfer;
+ pdev->iface.bulk_or_interrupt_transfer = libusb_udev_bulk_or_interrupt_transfer;
+ pdev->iface.select_interface = libusb_udev_select_interface;
+ pdev->iface.select_configuration = libusb_udev_select_configuration;
+ pdev->iface.complete_msconfig_setup = libusb_udev_complete_msconfig_setup;
+ pdev->iface.control_pipe_request = libusb_udev_control_pipe_request;
+ pdev->iface.control_query_device_text = libusb_udev_control_query_device_text;
+ pdev->iface.os_feature_descriptor_request = libusb_udev_os_feature_descriptor_request;
+ pdev->iface.cancel_all_transfer_request = libusb_udev_cancel_all_transfer_request;
+ pdev->iface.cancel_transfer_request = libusb_udev_cancel_transfer_request;
+ pdev->iface.query_device_descriptor = libusb_udev_query_device_descriptor;
+ pdev->iface.detach_kernel_driver = libusb_udev_detach_kernel_driver;
+ pdev->iface.attach_kernel_driver = libusb_udev_attach_kernel_driver;
+ pdev->iface.query_device_port_status = libusb_udev_query_device_port_status;
+ pdev->iface.free = udev_free;
+}
+
+static int udev_get_device_handle(URBDRC_PLUGIN* urbdrc, libusb_context* ctx, UDEVICE* pdev,
+ UINT16 bus_number, UINT16 dev_number)
+{
+ int error = -1;
+ uint8_t port_numbers[16] = { 0 };
+ LIBUSB_DEVICE** libusb_list = NULL;
+ const ssize_t total_device = libusb_get_device_list(ctx, &libusb_list);
+
+ WINPR_ASSERT(urbdrc);
+
+ /* Look for device. */
+ for (ssize_t i = 0; i < total_device; i++)
+ {
+ LIBUSB_DEVICE* dev = libusb_list[i];
+
+ if ((bus_number != libusb_get_bus_number(dev)) ||
+ (dev_number != libusb_get_device_address(dev)))
+ libusb_unref_device(dev);
+ else
+ {
+ error = libusb_open(dev, &pdev->libusb_handle);
+
+ if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_open", error))
+ {
+ libusb_unref_device(dev);
+ continue;
+ }
+
+ /* get port number */
+ error = libusb_get_port_numbers(dev, port_numbers, sizeof(port_numbers));
+ if (error < 1)
+ {
+ /* Prevent open hub, treat as error. */
+ log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_get_port_numbers", error);
+ libusb_unref_device(dev);
+ continue;
+ }
+
+ pdev->port_number = port_numbers[(error - 1)];
+ error = 0;
+ WLog_Print(urbdrc->log, WLOG_DEBUG, " Port: %d", pdev->port_number);
+ /* gen device path */
+ sprintf(pdev->path, "%" PRIu16 "-%d", bus_number, pdev->port_number);
+
+ WLog_Print(urbdrc->log, WLOG_DEBUG, " DevPath: %s", pdev->path);
+ }
+ }
+ libusb_free_device_list(libusb_list, 0);
+
+ if (error < 0)
+ return -1;
+ return 0;
+}
+
+static int udev_get_hub_handle(URBDRC_PLUGIN* urbdrc, libusb_context* ctx, UDEVICE* pdev,
+ UINT16 bus_number, UINT16 dev_number)
+{
+ int error = -1;
+ LIBUSB_DEVICE** libusb_list = NULL;
+ LIBUSB_DEVICE_HANDLE* handle = NULL;
+ const ssize_t total_device = libusb_get_device_list(ctx, &libusb_list);
+
+ WINPR_ASSERT(urbdrc);
+
+ /* Look for device hub. */
+ for (ssize_t i = 0; i < total_device; i++)
+ {
+ LIBUSB_DEVICE* dev = libusb_list[i];
+
+ if ((bus_number != libusb_get_bus_number(dev)) ||
+ (1 != libusb_get_device_address(dev))) /* Root hub allways first on bus. */
+ libusb_unref_device(dev);
+ else
+ {
+ WLog_Print(urbdrc->log, WLOG_DEBUG, " Open hub: %" PRIu16 "", bus_number);
+ error = libusb_open(dev, &handle);
+
+ if (!log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_open", error))
+ pdev->hub_handle = handle;
+ else
+ libusb_unref_device(dev);
+ }
+ }
+
+ libusb_free_device_list(libusb_list, 0);
+
+ if (error < 0)
+ return -1;
+
+ return 0;
+}
+
+static void request_free(void* value)
+{
+ ASYNC_TRANSFER_USER_DATA* user_data = NULL;
+ struct libusb_transfer* transfer = (struct libusb_transfer*)value;
+ if (!transfer)
+ return;
+
+ user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data;
+ async_transfer_user_data_free(user_data);
+ transfer->user_data = NULL;
+ libusb_free_transfer(transfer);
+}
+
+static IUDEVICE* udev_init(URBDRC_PLUGIN* urbdrc, libusb_context* context, LIBUSB_DEVICE* device,
+ BYTE bus_number, BYTE dev_number)
+{
+ UDEVICE* pdev = NULL;
+ int status = LIBUSB_ERROR_OTHER;
+ LIBUSB_DEVICE_DESCRIPTOR* devDescriptor = NULL;
+ LIBUSB_CONFIG_DESCRIPTOR* config_temp = NULL;
+ LIBUSB_INTERFACE_DESCRIPTOR interface_temp;
+
+ WINPR_ASSERT(urbdrc);
+
+ pdev = (PUDEVICE)calloc(1, sizeof(UDEVICE));
+
+ if (!pdev)
+ return NULL;
+
+ pdev->urbdrc = urbdrc;
+ udev_load_interface(pdev);
+
+ if (device)
+ pdev->libusb_dev = device;
+ else
+ pdev->libusb_dev = udev_get_libusb_dev(context, bus_number, dev_number);
+
+ if (pdev->libusb_dev == NULL)
+ goto fail;
+
+ if (urbdrc->listener_callback)
+ udev_set_channelManager(&pdev->iface, urbdrc->listener_callback->channel_mgr);
+
+ /* Get DEVICE handle */
+ status = udev_get_device_handle(urbdrc, context, pdev, bus_number, dev_number);
+ if (status != LIBUSB_SUCCESS)
+ {
+ struct libusb_device_descriptor desc;
+ const uint8_t port = libusb_get_port_number(pdev->libusb_dev);
+ libusb_get_device_descriptor(pdev->libusb_dev, &desc);
+
+ log_libusb_result(urbdrc->log, WLOG_ERROR,
+ "libusb_open [b=0x%02X,p=0x%02X,a=0x%02X,VID=0x%04X,PID=0x%04X]", status,
+ bus_number, port, dev_number, desc.idVendor, desc.idProduct);
+ goto fail;
+ }
+
+ /* Get HUB handle */
+ status = udev_get_hub_handle(urbdrc, context, pdev, bus_number, dev_number);
+
+ if (status < 0)
+ pdev->hub_handle = NULL;
+
+ pdev->devDescriptor = udev_new_descript(urbdrc, pdev->libusb_dev);
+
+ if (!pdev->devDescriptor)
+ goto fail;
+
+ status = libusb_get_active_config_descriptor(pdev->libusb_dev, &pdev->LibusbConfig);
+
+ if (status == LIBUSB_ERROR_NOT_FOUND)
+ status = libusb_get_config_descriptor(pdev->libusb_dev, 0, &pdev->LibusbConfig);
+
+ if (status < 0)
+ goto fail;
+
+ config_temp = pdev->LibusbConfig;
+ /* get the first interface and first altsetting */
+ interface_temp = config_temp->interface[0].altsetting[0];
+ WLog_Print(urbdrc->log, WLOG_DEBUG,
+ "Registered Device: Vid: 0x%04" PRIX16 " Pid: 0x%04" PRIX16 ""
+ " InterfaceClass = %s",
+ pdev->devDescriptor->idVendor, pdev->devDescriptor->idProduct,
+ usb_interface_class_to_string(interface_temp.bInterfaceClass));
+ /* Check composite device */
+ devDescriptor = pdev->devDescriptor;
+
+ if ((devDescriptor->bNumConfigurations == 1) && (config_temp->bNumInterfaces > 1) &&
+ (devDescriptor->bDeviceClass == LIBUSB_CLASS_PER_INTERFACE))
+ {
+ pdev->isCompositeDevice = 1;
+ }
+ else if ((devDescriptor->bDeviceClass == 0xef) &&
+ (devDescriptor->bDeviceSubClass == LIBUSB_CLASS_COMM) &&
+ (devDescriptor->bDeviceProtocol == 0x01))
+ {
+ pdev->isCompositeDevice = 1;
+ }
+ else
+ pdev->isCompositeDevice = 0;
+
+ /* set device class to first interface class */
+ devDescriptor->bDeviceClass = interface_temp.bInterfaceClass;
+ devDescriptor->bDeviceSubClass = interface_temp.bInterfaceSubClass;
+ devDescriptor->bDeviceProtocol = interface_temp.bInterfaceProtocol;
+ /* initialize pdev */
+ pdev->bus_number = bus_number;
+ pdev->dev_number = dev_number;
+ pdev->request_queue = ArrayList_New(TRUE);
+
+ if (!pdev->request_queue)
+ goto fail;
+
+ ArrayList_Object(pdev->request_queue)->fnObjectFree = request_free;
+
+ /* set config of windows */
+ pdev->MsConfig = msusb_msconfig_new();
+
+ if (!pdev->MsConfig)
+ goto fail;
+
+ // deb_config_msg(pdev->libusb_dev, config_temp, devDescriptor->bNumConfigurations);
+ return &pdev->iface;
+fail:
+ pdev->iface.free(&pdev->iface);
+ return NULL;
+}
+
+size_t udev_new_by_id(URBDRC_PLUGIN* urbdrc, libusb_context* ctx, UINT16 idVendor, UINT16 idProduct,
+ IUDEVICE*** devArray)
+{
+ LIBUSB_DEVICE** libusb_list = NULL;
+ UDEVICE** array = NULL;
+ ssize_t total_device = 0;
+ size_t num = 0;
+
+ if (!urbdrc || !devArray)
+ return 0;
+
+ WLog_Print(urbdrc->log, WLOG_INFO, "VID: 0x%04" PRIX16 ", PID: 0x%04" PRIX16 "", idVendor,
+ idProduct);
+ total_device = libusb_get_device_list(ctx, &libusb_list);
+ if (total_device < 0)
+ return 0;
+
+ array = (UDEVICE**)calloc((size_t)total_device, sizeof(UDEVICE*));
+
+ if (!array)
+ goto fail;
+
+ for (ssize_t i = 0; i < total_device; i++)
+ {
+ LIBUSB_DEVICE* dev = libusb_list[i];
+ LIBUSB_DEVICE_DESCRIPTOR* descriptor = udev_new_descript(urbdrc, dev);
+
+ if ((descriptor->idVendor == idVendor) && (descriptor->idProduct == idProduct))
+ {
+ array[num] = (PUDEVICE)udev_init(urbdrc, ctx, dev, libusb_get_bus_number(dev),
+ libusb_get_device_address(dev));
+
+ if (array[num] != NULL)
+ num++;
+ }
+ else
+ libusb_unref_device(dev);
+
+ free(descriptor);
+ }
+
+fail:
+ libusb_free_device_list(libusb_list, 0);
+ *devArray = (IUDEVICE**)array;
+ return num;
+}
+
+IUDEVICE* udev_new_by_addr(URBDRC_PLUGIN* urbdrc, libusb_context* context, BYTE bus_number,
+ BYTE dev_number)
+{
+ WLog_Print(urbdrc->log, WLOG_DEBUG, "bus:%d dev:%d", bus_number, dev_number);
+ return udev_init(urbdrc, context, NULL, bus_number, dev_number);
+}
diff --git a/channels/urbdrc/client/libusb/libusb_udevice.h b/channels/urbdrc/client/libusb/libusb_udevice.h
new file mode 100644
index 0000000..33705e3
--- /dev/null
+++ b/channels/urbdrc/client/libusb/libusb_udevice.h
@@ -0,0 +1,76 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX USB Redirection
+ *
+ * Copyright 2012 Atrust corp.
+ * Copyright 2012 Alfred Liu <alfred.liu@atruscorp.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_URBDRC_CLIENT_LIBUSB_UDEVICE_H
+#define FREERDP_CHANNEL_URBDRC_CLIENT_LIBUSB_UDEVICE_H
+
+#include <winpr/windows.h>
+#include <libusb.h>
+
+#include "urbdrc_types.h"
+#include "urbdrc_main.h"
+
+typedef struct libusb_device LIBUSB_DEVICE;
+typedef struct libusb_device_handle LIBUSB_DEVICE_HANDLE;
+typedef struct libusb_device_descriptor LIBUSB_DEVICE_DESCRIPTOR;
+typedef struct libusb_config_descriptor LIBUSB_CONFIG_DESCRIPTOR;
+typedef struct libusb_interface LIBUSB_INTERFACE;
+typedef struct libusb_interface_descriptor LIBUSB_INTERFACE_DESCRIPTOR;
+typedef struct libusb_endpoint_descriptor LIBUSB_ENDPOINT_DESCEIPTOR;
+
+typedef struct
+{
+ IUDEVICE iface;
+
+ void* udev;
+ void* prev;
+ void* next;
+
+ UINT32 UsbDevice; /* An unique interface ID */
+ UINT32 ReqCompletion; /* An unique interface ID */
+ IWTSVirtualChannelManager* channelManager;
+ UINT32 channelID;
+ UINT16 status;
+ BYTE bus_number;
+ BYTE dev_number;
+ char path[17];
+ int port_number;
+ int isCompositeDevice;
+
+ LIBUSB_DEVICE_HANDLE* libusb_handle;
+ LIBUSB_DEVICE_HANDLE* hub_handle;
+ LIBUSB_DEVICE* libusb_dev;
+ LIBUSB_DEVICE_DESCRIPTOR* devDescriptor;
+ MSUSB_CONFIG_DESCRIPTOR* MsConfig;
+ LIBUSB_CONFIG_DESCRIPTOR* LibusbConfig;
+
+ wArrayList* request_queue;
+
+ URBDRC_PLUGIN* urbdrc;
+} UDEVICE;
+typedef UDEVICE* PUDEVICE;
+
+size_t udev_new_by_id(URBDRC_PLUGIN* urbdrc, libusb_context* ctx, UINT16 idVendor, UINT16 idProduct,
+ IUDEVICE*** devArray);
+IUDEVICE* udev_new_by_addr(URBDRC_PLUGIN* urbdrc, libusb_context* ctx, BYTE bus_number,
+ BYTE dev_number);
+const char* usb_interface_class_to_string(uint8_t class);
+
+#endif /* FREERDP_CHANNEL_URBDRC_CLIENT_LIBUSB_UDEVICE_H */
diff --git a/channels/urbdrc/client/libusb/libusb_udevman.c b/channels/urbdrc/client/libusb/libusb_udevman.c
new file mode 100644
index 0000000..d52c307
--- /dev/null
+++ b/channels/urbdrc/client/libusb/libusb_udevman.c
@@ -0,0 +1,970 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX USB Redirection
+ *
+ * Copyright 2012 Atrust corp.
+ * Copyright 2012 Alfred Liu <alfred.liu@atruscorp.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <winpr/crt.h>
+#include <winpr/cmdline.h>
+#include <winpr/collections.h>
+
+#include <freerdp/addin.h>
+
+#include "urbdrc_types.h"
+#include "urbdrc_main.h"
+
+#include "libusb_udevice.h"
+
+#include <libusb.h>
+
+#if !defined(LIBUSB_HOTPLUG_NO_FLAGS)
+#define LIBUSB_HOTPLUG_NO_FLAGS 0
+#endif
+
+#define BASIC_STATE_FUNC_DEFINED(_arg, _type) \
+ static _type udevman_get_##_arg(IUDEVMAN* idevman) \
+ { \
+ UDEVMAN* udevman = (UDEVMAN*)idevman; \
+ return udevman->_arg; \
+ } \
+ static void udevman_set_##_arg(IUDEVMAN* idevman, _type _t) \
+ { \
+ UDEVMAN* udevman = (UDEVMAN*)idevman; \
+ udevman->_arg = _t; \
+ }
+
+#define BASIC_STATE_FUNC_REGISTER(_arg, _man) \
+ _man->iface.get_##_arg = udevman_get_##_arg; \
+ _man->iface.set_##_arg = udevman_set_##_arg
+
+typedef struct
+{
+ UINT16 vid;
+ UINT16 pid;
+} VID_PID_PAIR;
+
+typedef struct
+{
+ IUDEVMAN iface;
+
+ IUDEVICE* idev; /* iterator device */
+ IUDEVICE* head; /* head device in linked list */
+ IUDEVICE* tail; /* tail device in linked list */
+
+ LPCSTR devices_vid_pid;
+ LPCSTR devices_addr;
+ wArrayList* hotplug_vid_pids;
+ UINT16 flags;
+ UINT32 device_num;
+ UINT32 next_device_id;
+ UINT32 channel_id;
+
+ HANDLE devman_loading;
+ libusb_context* context;
+ HANDLE thread;
+ BOOL running;
+} UDEVMAN;
+typedef UDEVMAN* PUDEVMAN;
+
+static BOOL poll_libusb_events(UDEVMAN* udevman);
+
+static void udevman_rewind(IUDEVMAN* idevman)
+{
+ UDEVMAN* udevman = (UDEVMAN*)idevman;
+ udevman->idev = udevman->head;
+}
+
+static BOOL udevman_has_next(IUDEVMAN* idevman)
+{
+ UDEVMAN* udevman = (UDEVMAN*)idevman;
+
+ if (!udevman || !udevman->idev)
+ return FALSE;
+ else
+ return TRUE;
+}
+
+static IUDEVICE* udevman_get_next(IUDEVMAN* idevman)
+{
+ UDEVMAN* udevman = (UDEVMAN*)idevman;
+ IUDEVICE* pdev = NULL;
+ pdev = udevman->idev;
+ udevman->idev = (IUDEVICE*)((UDEVICE*)udevman->idev)->next;
+ return pdev;
+}
+
+static IUDEVICE* udevman_get_udevice_by_addr(IUDEVMAN* idevman, BYTE bus_number, BYTE dev_number)
+{
+ IUDEVICE* dev = NULL;
+
+ if (!idevman)
+ return NULL;
+
+ idevman->loading_lock(idevman);
+ idevman->rewind(idevman);
+
+ while (idevman->has_next(idevman))
+ {
+ IUDEVICE* pdev = idevman->get_next(idevman);
+
+ if ((pdev->get_bus_number(pdev) == bus_number) &&
+ (pdev->get_dev_number(pdev) == dev_number))
+ {
+ dev = pdev;
+ break;
+ }
+ }
+
+ idevman->loading_unlock(idevman);
+ return dev;
+}
+
+static size_t udevman_register_udevice(IUDEVMAN* idevman, BYTE bus_number, BYTE dev_number,
+ UINT16 idVendor, UINT16 idProduct, UINT32 flag)
+{
+ UDEVMAN* udevman = (UDEVMAN*)idevman;
+ IUDEVICE* pdev = NULL;
+ IUDEVICE** devArray = NULL;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ size_t num = 0;
+ size_t addnum = 0;
+
+ if (!idevman || !idevman->plugin)
+ return 0;
+
+ urbdrc = (URBDRC_PLUGIN*)idevman->plugin;
+ pdev = (IUDEVICE*)udevman_get_udevice_by_addr(idevman, bus_number, dev_number);
+
+ if (pdev != NULL)
+ return 0;
+
+ if (flag & UDEVMAN_FLAG_ADD_BY_ADDR)
+ {
+ UINT32 id = 0;
+ IUDEVICE* tdev = udev_new_by_addr(urbdrc, udevman->context, bus_number, dev_number);
+
+ if (tdev == NULL)
+ return 0;
+
+ id = idevman->get_next_device_id(idevman);
+ tdev->set_UsbDevice(tdev, id);
+ idevman->loading_lock(idevman);
+
+ if (udevman->head == NULL)
+ {
+ /* linked list is empty */
+ udevman->head = tdev;
+ udevman->tail = tdev;
+ }
+ else
+ {
+ /* append device to the end of the linked list */
+ udevman->tail->set_p_next(udevman->tail, tdev);
+ tdev->set_p_prev(tdev, udevman->tail);
+ udevman->tail = tdev;
+ }
+
+ udevman->device_num += 1;
+ idevman->loading_unlock(idevman);
+ }
+ else if (flag & UDEVMAN_FLAG_ADD_BY_VID_PID)
+ {
+ addnum = 0;
+ /* register all device that match pid vid */
+ num = udev_new_by_id(urbdrc, udevman->context, idVendor, idProduct, &devArray);
+
+ if (num == 0)
+ {
+ WLog_Print(urbdrc->log, WLOG_WARN,
+ "Could not find or redirect any usb devices by id %04x:%04x", idVendor,
+ idProduct);
+ }
+
+ for (size_t i = 0; i < num; i++)
+ {
+ UINT32 id = 0;
+ IUDEVICE* tdev = devArray[i];
+
+ if (udevman_get_udevice_by_addr(idevman, tdev->get_bus_number(tdev),
+ tdev->get_dev_number(tdev)) != NULL)
+ {
+ tdev->free(tdev);
+ devArray[i] = NULL;
+ continue;
+ }
+
+ id = idevman->get_next_device_id(idevman);
+ tdev->set_UsbDevice(tdev, id);
+ idevman->loading_lock(idevman);
+
+ if (udevman->head == NULL)
+ {
+ /* linked list is empty */
+ udevman->head = tdev;
+ udevman->tail = tdev;
+ }
+ else
+ {
+ /* append device to the end of the linked list */
+ udevman->tail->set_p_next(udevman->tail, tdev);
+ tdev->set_p_prev(tdev, udevman->tail);
+ udevman->tail = tdev;
+ }
+
+ udevman->device_num += 1;
+ idevman->loading_unlock(idevman);
+ addnum++;
+ }
+
+ free(devArray);
+ return addnum;
+ }
+ else
+ {
+ WLog_Print(urbdrc->log, WLOG_ERROR, "udevman_register_udevice: Invalid flag=%08" PRIx32,
+ flag);
+ return 0;
+ }
+
+ return 1;
+}
+
+static BOOL udevman_unregister_udevice(IUDEVMAN* idevman, BYTE bus_number, BYTE dev_number)
+{
+ UDEVMAN* udevman = (UDEVMAN*)idevman;
+ UDEVICE* pdev = NULL;
+ UDEVICE* dev = (UDEVICE*)udevman_get_udevice_by_addr(idevman, bus_number, dev_number);
+
+ if (!dev || !idevman)
+ return FALSE;
+
+ idevman->loading_lock(idevman);
+ idevman->rewind(idevman);
+
+ while (idevman->has_next(idevman))
+ {
+ pdev = (UDEVICE*)idevman->get_next(idevman);
+
+ if (pdev == dev) /* device exists */
+ {
+ /* set previous device to point to next device */
+ if (dev->prev != NULL)
+ {
+ /* unregistered device is not the head */
+ pdev = dev->prev;
+ pdev->next = dev->next;
+ }
+ else
+ {
+ /* unregistered device is the head, update head */
+ udevman->head = (IUDEVICE*)dev->next;
+ }
+
+ /* set next device to point to previous device */
+
+ if (dev->next != NULL)
+ {
+ /* unregistered device is not the tail */
+ pdev = (UDEVICE*)dev->next;
+ pdev->prev = dev->prev;
+ }
+ else
+ {
+ /* unregistered device is the tail, update tail */
+ udevman->tail = (IUDEVICE*)dev->prev;
+ }
+
+ udevman->device_num--;
+ break;
+ }
+ }
+
+ idevman->loading_unlock(idevman);
+
+ if (dev)
+ {
+ dev->iface.free(&dev->iface);
+ return TRUE; /* unregistration successful */
+ }
+
+ /* if we reach this point, the device wasn't found */
+ return FALSE;
+}
+
+static BOOL udevman_unregister_all_udevices(IUDEVMAN* idevman)
+{
+ UDEVMAN* udevman = (UDEVMAN*)idevman;
+
+ if (!idevman)
+ return FALSE;
+
+ if (!udevman->head)
+ return TRUE;
+
+ idevman->loading_lock(idevman);
+ idevman->rewind(idevman);
+
+ while (idevman->has_next(idevman))
+ {
+ UDEVICE* dev = (UDEVICE*)idevman->get_next(idevman);
+
+ if (!dev)
+ continue;
+
+ /* set previous device to point to next device */
+ if (dev->prev != NULL)
+ {
+ /* unregistered device is not the head */
+ UDEVICE* pdev = dev->prev;
+ pdev->next = dev->next;
+ }
+ else
+ {
+ /* unregistered device is the head, update head */
+ udevman->head = (IUDEVICE*)dev->next;
+ }
+
+ /* set next device to point to previous device */
+
+ if (dev->next != NULL)
+ {
+ /* unregistered device is not the tail */
+ UDEVICE* pdev = (UDEVICE*)dev->next;
+ pdev->prev = dev->prev;
+ }
+ else
+ {
+ /* unregistered device is the tail, update tail */
+ udevman->tail = (IUDEVICE*)dev->prev;
+ }
+
+ dev->iface.free(&dev->iface);
+ udevman->device_num--;
+ }
+
+ idevman->loading_unlock(idevman);
+
+ return TRUE;
+}
+
+static int udevman_is_auto_add(IUDEVMAN* idevman)
+{
+ UDEVMAN* udevman = (UDEVMAN*)idevman;
+ return (udevman->flags & UDEVMAN_FLAG_ADD_BY_AUTO) ? 1 : 0;
+}
+
+static IUDEVICE* udevman_get_udevice_by_UsbDevice(IUDEVMAN* idevman, UINT32 UsbDevice)
+{
+ UDEVICE* pdev = NULL;
+ URBDRC_PLUGIN* urbdrc = NULL;
+
+ if (!idevman || !idevman->plugin)
+ return NULL;
+
+ /* Mask highest 2 bits, must be ignored */
+ UsbDevice = UsbDevice & INTERFACE_ID_MASK;
+ urbdrc = (URBDRC_PLUGIN*)idevman->plugin;
+ idevman->loading_lock(idevman);
+ idevman->rewind(idevman);
+
+ while (idevman->has_next(idevman))
+ {
+ pdev = (UDEVICE*)idevman->get_next(idevman);
+
+ if (pdev->UsbDevice == UsbDevice)
+ {
+ idevman->loading_unlock(idevman);
+ return (IUDEVICE*)pdev;
+ }
+ }
+
+ idevman->loading_unlock(idevman);
+ WLog_Print(urbdrc->log, WLOG_WARN, "Failed to find a USB device mapped to deviceId=%08" PRIx32,
+ UsbDevice);
+ return NULL;
+}
+
+static IUDEVICE* udevman_get_udevice_by_ChannelID(IUDEVMAN* idevman, UINT32 channelID)
+{
+ UDEVICE* pdev = NULL;
+ URBDRC_PLUGIN* urbdrc = NULL;
+
+ if (!idevman || !idevman->plugin)
+ return NULL;
+
+ /* Mask highest 2 bits, must be ignored */
+ urbdrc = (URBDRC_PLUGIN*)idevman->plugin;
+ idevman->loading_lock(idevman);
+ idevman->rewind(idevman);
+
+ while (idevman->has_next(idevman))
+ {
+ pdev = (UDEVICE*)idevman->get_next(idevman);
+
+ if (pdev->channelID == channelID)
+ {
+ idevman->loading_unlock(idevman);
+ return (IUDEVICE*)pdev;
+ }
+ }
+
+ idevman->loading_unlock(idevman);
+ WLog_Print(urbdrc->log, WLOG_WARN, "Failed to find a USB device mapped to channelID=%08" PRIx32,
+ channelID);
+ return NULL;
+}
+
+static void udevman_loading_lock(IUDEVMAN* idevman)
+{
+ UDEVMAN* udevman = (UDEVMAN*)idevman;
+ WaitForSingleObject(udevman->devman_loading, INFINITE);
+}
+
+static void udevman_loading_unlock(IUDEVMAN* idevman)
+{
+ UDEVMAN* udevman = (UDEVMAN*)idevman;
+ ReleaseMutex(udevman->devman_loading);
+}
+
+BASIC_STATE_FUNC_DEFINED(device_num, UINT32)
+
+static UINT32 udevman_get_next_device_id(IUDEVMAN* idevman)
+{
+ UDEVMAN* udevman = (UDEVMAN*)idevman;
+ return udevman->next_device_id++;
+}
+
+static void udevman_set_next_device_id(IUDEVMAN* idevman, UINT32 _t)
+{
+ UDEVMAN* udevman = (UDEVMAN*)idevman;
+ udevman->next_device_id = _t;
+}
+
+static void udevman_free(IUDEVMAN* idevman)
+{
+ UDEVMAN* udevman = (UDEVMAN*)idevman;
+
+ if (!udevman)
+ return;
+
+ udevman->running = FALSE;
+ if (udevman->thread)
+ {
+ WaitForSingleObject(udevman->thread, INFINITE);
+ CloseHandle(udevman->thread);
+ }
+
+ udevman_unregister_all_udevices(idevman);
+
+ if (udevman->devman_loading)
+ CloseHandle(udevman->devman_loading);
+
+ libusb_exit(udevman->context);
+
+ ArrayList_Free(udevman->hotplug_vid_pids);
+ free(udevman);
+}
+
+static BOOL filter_by_class(uint8_t bDeviceClass, uint8_t bDeviceSubClass)
+{
+ switch (bDeviceClass)
+ {
+ case LIBUSB_CLASS_AUDIO:
+ case LIBUSB_CLASS_HID:
+ case LIBUSB_CLASS_MASS_STORAGE:
+ case LIBUSB_CLASS_HUB:
+ case LIBUSB_CLASS_SMART_CARD:
+ return TRUE;
+ default:
+ break;
+ }
+
+ switch (bDeviceSubClass)
+ {
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+static BOOL append(char* dst, size_t length, const char* src)
+{
+ return winpr_str_append(src, dst, length, NULL);
+}
+
+static BOOL device_is_filtered(struct libusb_device* dev,
+ const struct libusb_device_descriptor* desc,
+ libusb_hotplug_event event)
+{
+ char buffer[8192] = { 0 };
+ char* what = NULL;
+ BOOL filtered = FALSE;
+ append(buffer, sizeof(buffer), usb_interface_class_to_string(desc->bDeviceClass));
+ if (filter_by_class(desc->bDeviceClass, desc->bDeviceSubClass))
+ filtered = TRUE;
+
+ switch (desc->bDeviceClass)
+ {
+ case LIBUSB_CLASS_PER_INTERFACE:
+ {
+ struct libusb_config_descriptor* config = NULL;
+ int rc = libusb_get_active_config_descriptor(dev, &config);
+ if (rc == LIBUSB_SUCCESS)
+ {
+ for (uint8_t x = 0; x < config->bNumInterfaces; x++)
+ {
+ const struct libusb_interface* ifc = &config->interface[x];
+ for (int y = 0; y < ifc->num_altsetting; y++)
+ {
+ const struct libusb_interface_descriptor* const alt = &ifc->altsetting[y];
+ if (filter_by_class(alt->bInterfaceClass, alt->bInterfaceSubClass))
+ filtered = TRUE;
+
+ append(buffer, sizeof(buffer), "|");
+ append(buffer, sizeof(buffer),
+ usb_interface_class_to_string(alt->bInterfaceClass));
+ }
+ }
+ }
+ libusb_free_config_descriptor(config);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (filtered)
+ what = "Filtered";
+ else
+ {
+ switch (event)
+ {
+ case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT:
+ what = "Hotplug remove";
+ break;
+ case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED:
+ what = "Hotplug add";
+ break;
+ default:
+ what = "Hotplug unknown";
+ break;
+ }
+ }
+
+ WLog_DBG(TAG, "%s device VID=0x%04X,PID=0x%04X class %s", what, desc->idVendor, desc->idProduct,
+ buffer);
+ return filtered;
+}
+
+static int LIBUSB_CALL hotplug_callback(struct libusb_context* ctx, struct libusb_device* dev,
+ libusb_hotplug_event event, void* user_data)
+{
+ VID_PID_PAIR pair;
+ struct libusb_device_descriptor desc;
+ UDEVMAN* udevman = (UDEVMAN*)user_data;
+ const uint8_t bus = libusb_get_bus_number(dev);
+ const uint8_t addr = libusb_get_device_address(dev);
+ int rc = libusb_get_device_descriptor(dev, &desc);
+
+ WINPR_UNUSED(ctx);
+
+ if (rc != LIBUSB_SUCCESS)
+ return rc;
+
+ switch (event)
+ {
+ case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED:
+ pair.vid = desc.idVendor;
+ pair.pid = desc.idProduct;
+ if ((ArrayList_Contains(udevman->hotplug_vid_pids, &pair)) ||
+ (udevman->iface.isAutoAdd(&udevman->iface) &&
+ !device_is_filtered(dev, &desc, event)))
+ {
+ add_device(&udevman->iface, DEVICE_ADD_FLAG_ALL, bus, addr, desc.idVendor,
+ desc.idProduct);
+ }
+ break;
+
+ case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT:
+ del_device(&udevman->iface, DEVICE_ADD_FLAG_ALL, bus, addr, desc.idVendor,
+ desc.idProduct);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static BOOL udevman_initialize(IUDEVMAN* idevman, UINT32 channelId)
+{
+ UDEVMAN* udevman = (UDEVMAN*)idevman;
+
+ if (!udevman)
+ return FALSE;
+
+ idevman->status &= ~URBDRC_DEVICE_CHANNEL_CLOSED;
+ idevman->controlChannelId = channelId;
+ return TRUE;
+}
+
+static BOOL udevman_vid_pid_pair_equals(const void* objA, const void* objB)
+{
+ const VID_PID_PAIR* a = objA;
+ const VID_PID_PAIR* b = objB;
+
+ return (a->vid == b->vid) && (a->pid == b->pid);
+}
+
+static BOOL udevman_parse_device_id_addr(const char** str, UINT16* id1, UINT16* id2, UINT16 max,
+ char split_sign, char delimiter)
+{
+ char* mid = NULL;
+ char* end = NULL;
+ unsigned long rc = 0;
+
+ rc = strtoul(*str, &mid, 16);
+
+ if ((mid == *str) || (*mid != split_sign) || (rc > max))
+ return FALSE;
+
+ *id1 = (UINT16)rc;
+ rc = strtoul(++mid, &end, 16);
+
+ if ((end == mid) || (rc > max))
+ return FALSE;
+
+ *id2 = (UINT16)rc;
+
+ *str += end - *str;
+ if (*end == '\0')
+ return TRUE;
+ if (*end == delimiter)
+ {
+ (*str)++;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL urbdrc_udevman_register_devices(UDEVMAN* udevman, const char* devices, BOOL add_by_addr)
+{
+ const char* pos = devices;
+ VID_PID_PAIR* idpair = NULL;
+ UINT16 id1 = 0;
+ UINT16 id2 = 0;
+
+ while (*pos != '\0')
+ {
+ if (!udevman_parse_device_id_addr(&pos, &id1, &id2, (add_by_addr) ? UINT8_MAX : UINT16_MAX,
+ ':', '#'))
+ {
+ WLog_ERR(TAG, "Invalid device argument: \"%s\"", devices);
+ return FALSE;
+ }
+
+ if (add_by_addr)
+ {
+ add_device(&udevman->iface, DEVICE_ADD_FLAG_BUS | DEVICE_ADD_FLAG_DEV, (UINT8)id1,
+ (UINT8)id2, 0, 0);
+ }
+ else
+ {
+ idpair = calloc(1, sizeof(VID_PID_PAIR));
+ if (!idpair)
+ return CHANNEL_RC_NO_MEMORY;
+ idpair->vid = id1;
+ idpair->pid = id2;
+ if (!ArrayList_Append(udevman->hotplug_vid_pids, idpair))
+ {
+ free(idpair);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ add_device(&udevman->iface, DEVICE_ADD_FLAG_VENDOR | DEVICE_ADD_FLAG_PRODUCT, 0, 0, id1,
+ id2);
+ }
+ }
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): ArrayList_Append owns idpair
+ return CHANNEL_RC_OK;
+}
+
+static UINT urbdrc_udevman_parse_addin_args(UDEVMAN* udevman, const ADDIN_ARGV* args)
+{
+ LPCSTR devices = NULL;
+
+ for (int x = 0; x < args->argc; x++)
+ {
+ const char* arg = args->argv[x];
+ if (strcmp(arg, "dbg") == 0)
+ {
+ WLog_SetLogLevel(WLog_Get(TAG), WLOG_TRACE);
+ }
+ else if (_strnicmp(arg, "device:", 7) == 0)
+ {
+ /* Redirect all local devices */
+ const char* val = &arg[7];
+ const size_t len = strlen(val);
+ if (strcmp(val, "*") == 0)
+ {
+ udevman->flags |= UDEVMAN_FLAG_ADD_BY_AUTO;
+ }
+ else if (_strnicmp(arg, "USBInstanceID:", 14) == 0)
+ {
+ // TODO: Usb instance ID
+ }
+ else if ((val[0] == '{') && (val[len - 1] == '}'))
+ {
+ // TODO: Usb device class
+ }
+ }
+ else if (_strnicmp(arg, "dev:", 4) == 0)
+ {
+ devices = &arg[4];
+ }
+ else if (_strnicmp(arg, "id", 2) == 0)
+ {
+ const char* p = strchr(arg, ':');
+ if (p)
+ udevman->devices_vid_pid = p + 1;
+ else
+ udevman->flags = UDEVMAN_FLAG_ADD_BY_VID_PID;
+ }
+ else if (_strnicmp(arg, "addr", 4) == 0)
+ {
+ const char* p = strchr(arg, ':');
+ if (p)
+ udevman->devices_addr = p + 1;
+ else
+ udevman->flags = UDEVMAN_FLAG_ADD_BY_ADDR;
+ }
+ else if (strcmp(arg, "auto") == 0)
+ {
+ udevman->flags |= UDEVMAN_FLAG_ADD_BY_AUTO;
+ }
+ else
+ {
+ const size_t len = strlen(arg);
+ if ((arg[0] == '{') && (arg[len - 1] == '}'))
+ {
+ // TODO: Check for {Device Setup Class GUID}:
+ }
+ }
+ }
+ if (devices)
+ {
+ if (udevman->flags & UDEVMAN_FLAG_ADD_BY_VID_PID)
+ udevman->devices_vid_pid = devices;
+ else if (udevman->flags & UDEVMAN_FLAG_ADD_BY_ADDR)
+ udevman->devices_addr = devices;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT udevman_listener_created_callback(IUDEVMAN* iudevman)
+{
+ UINT status = 0;
+ UDEVMAN* udevman = (UDEVMAN*)iudevman;
+
+ if (udevman->devices_vid_pid)
+ {
+ status = urbdrc_udevman_register_devices(udevman, udevman->devices_vid_pid, FALSE);
+ if (status != CHANNEL_RC_OK)
+ return status;
+ }
+
+ if (udevman->devices_addr)
+ return urbdrc_udevman_register_devices(udevman, udevman->devices_addr, TRUE);
+
+ return CHANNEL_RC_OK;
+}
+
+static void udevman_load_interface(UDEVMAN* udevman)
+{
+ /* standard */
+ udevman->iface.free = udevman_free;
+ /* manage devices */
+ udevman->iface.rewind = udevman_rewind;
+ udevman->iface.get_next = udevman_get_next;
+ udevman->iface.has_next = udevman_has_next;
+ udevman->iface.register_udevice = udevman_register_udevice;
+ udevman->iface.unregister_udevice = udevman_unregister_udevice;
+ udevman->iface.get_udevice_by_UsbDevice = udevman_get_udevice_by_UsbDevice;
+ udevman->iface.get_udevice_by_ChannelID = udevman_get_udevice_by_ChannelID;
+ /* Extension */
+ udevman->iface.isAutoAdd = udevman_is_auto_add;
+ /* Basic state */
+ BASIC_STATE_FUNC_REGISTER(device_num, udevman);
+ BASIC_STATE_FUNC_REGISTER(next_device_id, udevman);
+
+ /* control semaphore or mutex lock */
+ udevman->iface.loading_lock = udevman_loading_lock;
+ udevman->iface.loading_unlock = udevman_loading_unlock;
+ udevman->iface.initialize = udevman_initialize;
+ udevman->iface.listener_created_callback = udevman_listener_created_callback;
+}
+
+static BOOL poll_libusb_events(UDEVMAN* udevman)
+{
+ int rc = LIBUSB_SUCCESS;
+ struct timeval tv = { 0, 500 };
+ if (libusb_try_lock_events(udevman->context) == 0)
+ {
+ if (libusb_event_handling_ok(udevman->context))
+ {
+ rc = libusb_handle_events_locked(udevman->context, &tv);
+ if (rc != LIBUSB_SUCCESS)
+ WLog_WARN(TAG, "libusb_handle_events_locked %d", rc);
+ }
+ libusb_unlock_events(udevman->context);
+ }
+ else
+ {
+ libusb_lock_event_waiters(udevman->context);
+ if (libusb_event_handler_active(udevman->context))
+ {
+ rc = libusb_wait_for_event(udevman->context, &tv);
+ if (rc < LIBUSB_SUCCESS)
+ WLog_WARN(TAG, "libusb_wait_for_event %d", rc);
+ }
+ libusb_unlock_event_waiters(udevman->context);
+ }
+
+ return rc > 0;
+}
+
+static DWORD WINAPI poll_thread(LPVOID lpThreadParameter)
+{
+ libusb_hotplug_callback_handle handle = 0;
+ UDEVMAN* udevman = (UDEVMAN*)lpThreadParameter;
+ BOOL hasHotplug = libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG);
+
+ if (hasHotplug)
+ {
+ int rc = libusb_hotplug_register_callback(
+ udevman->context,
+ LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
+ LIBUSB_HOTPLUG_NO_FLAGS, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY,
+ LIBUSB_HOTPLUG_MATCH_ANY, hotplug_callback, udevman, &handle);
+
+ if (rc != LIBUSB_SUCCESS)
+ udevman->running = FALSE;
+ }
+ else
+ WLog_WARN(TAG, "Platform does not support libusb hotplug. USB devices plugged in later "
+ "will not be detected.");
+
+ while (udevman->running)
+ {
+ poll_libusb_events(udevman);
+ }
+
+ if (hasHotplug)
+ libusb_hotplug_deregister_callback(udevman->context, handle);
+
+ /* Process remaining usb events */
+ while (poll_libusb_events(udevman))
+ ;
+
+ ExitThread(0);
+ return 0;
+}
+
+FREERDP_ENTRY_POINT(UINT libusb_freerdp_urbdrc_client_subsystem_entry(
+ PFREERDP_URBDRC_SERVICE_ENTRY_POINTS pEntryPoints))
+{
+ wObject* obj = NULL;
+ UINT rc = 0;
+ UINT status = 0;
+ UDEVMAN* udevman = NULL;
+ const ADDIN_ARGV* args = pEntryPoints->args;
+ udevman = (PUDEVMAN)calloc(1, sizeof(UDEVMAN));
+
+ if (!udevman)
+ goto fail;
+
+ udevman->hotplug_vid_pids = ArrayList_New(TRUE);
+ if (!udevman->hotplug_vid_pids)
+ goto fail;
+ obj = ArrayList_Object(udevman->hotplug_vid_pids);
+ obj->fnObjectFree = free;
+ obj->fnObjectEquals = udevman_vid_pid_pair_equals;
+
+ udevman->next_device_id = BASE_USBDEVICE_NUM;
+ udevman->iface.plugin = pEntryPoints->plugin;
+ rc = libusb_init(&udevman->context);
+
+ if (rc != LIBUSB_SUCCESS)
+ goto fail;
+
+#ifdef _WIN32
+#if LIBUSB_API_VERSION >= 0x01000106
+ /* Prefer usbDK backend on windows. Not supported on other platforms. */
+ rc = libusb_set_option(udevman->context, LIBUSB_OPTION_USE_USBDK);
+ switch (rc)
+ {
+ case LIBUSB_SUCCESS:
+ break;
+ case LIBUSB_ERROR_NOT_FOUND:
+ case LIBUSB_ERROR_NOT_SUPPORTED:
+ WLog_WARN(TAG, "LIBUSB_OPTION_USE_USBDK %s [%d]", libusb_strerror(rc), rc);
+ break;
+ default:
+ WLog_ERR(TAG, "LIBUSB_OPTION_USE_USBDK %s [%d]", libusb_strerror(rc), rc);
+ goto fail;
+ }
+#endif
+#endif
+
+ udevman->flags = UDEVMAN_FLAG_ADD_BY_VID_PID;
+ udevman->devman_loading = CreateMutexA(NULL, FALSE, "devman_loading");
+
+ if (!udevman->devman_loading)
+ goto fail;
+
+ /* load usb device service management */
+ udevman_load_interface(udevman);
+ status = urbdrc_udevman_parse_addin_args(udevman, args);
+
+ if (status != CHANNEL_RC_OK)
+ goto fail;
+
+ udevman->running = TRUE;
+ udevman->thread = CreateThread(NULL, 0, poll_thread, udevman, 0, NULL);
+
+ if (!udevman->thread)
+ goto fail;
+
+ if (!pEntryPoints->pRegisterUDEVMAN(pEntryPoints->plugin, (IUDEVMAN*)udevman))
+ goto fail;
+
+ WLog_DBG(TAG, "UDEVMAN device registered.");
+ return 0;
+fail:
+ udevman_free(&udevman->iface);
+ return ERROR_INTERNAL_ERROR;
+}
diff --git a/channels/urbdrc/client/urbdrc_main.c b/channels/urbdrc/client/urbdrc_main.c
new file mode 100644
index 0000000..96e443d
--- /dev/null
+++ b/channels/urbdrc/client/urbdrc_main.c
@@ -0,0 +1,1023 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX USB Redirection
+ *
+ * Copyright 2012 Atrust corp.
+ * Copyright 2012 Alfred Liu <alfred.liu@atruscorp.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <winpr/assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <winpr/pool.h>
+#include <winpr/print.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/string.h>
+#include <winpr/cmdline.h>
+
+#include <freerdp/dvc.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/channels/urbdrc.h>
+
+#include "urbdrc_types.h"
+#include "urbdrc_main.h"
+#include "data_transfer.h"
+
+#include <urbdrc_helpers.h>
+
+static IWTSVirtualChannel* get_channel(IUDEVMAN* idevman)
+{
+ IWTSVirtualChannelManager* channel_mgr = NULL;
+ URBDRC_PLUGIN* urbdrc = NULL;
+
+ if (!idevman)
+ return NULL;
+
+ urbdrc = (URBDRC_PLUGIN*)idevman->plugin;
+
+ if (!urbdrc || !urbdrc->listener_callback)
+ return NULL;
+
+ channel_mgr = urbdrc->listener_callback->channel_mgr;
+
+ if (!channel_mgr)
+ return NULL;
+
+ return channel_mgr->FindChannelById(channel_mgr, idevman->controlChannelId);
+}
+
+static int func_container_id_generate(IUDEVICE* pdev, char* strContainerId)
+{
+ char* p = NULL;
+ char* path = NULL;
+ UINT8 containerId[17] = { 0 };
+ UINT16 idVendor = 0;
+ UINT16 idProduct = 0;
+ idVendor = (UINT16)pdev->query_device_descriptor(pdev, ID_VENDOR);
+ idProduct = (UINT16)pdev->query_device_descriptor(pdev, ID_PRODUCT);
+ path = pdev->getPath(pdev);
+
+ if (strlen(path) > 8)
+ p = (path + strlen(path)) - 8;
+ else
+ p = path;
+
+ sprintf_s((char*)containerId, sizeof(containerId), "%04" PRIX16 "%04" PRIX16 "%s", idVendor,
+ idProduct, p);
+ /* format */
+ sprintf_s(strContainerId, DEVICE_CONTAINER_STR_SIZE,
+ "{%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "-%02" PRIx8 "%02" PRIx8 "-%02" PRIx8
+ "%02" PRIx8 "-%02" PRIx8 "%02" PRIx8 "-%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8
+ "%02" PRIx8 "%02" PRIx8 "}",
+ containerId[0], containerId[1], containerId[2], containerId[3], containerId[4],
+ containerId[5], containerId[6], containerId[7], containerId[8], containerId[9],
+ containerId[10], containerId[11], containerId[12], containerId[13], containerId[14],
+ containerId[15]);
+ return 0;
+}
+
+static int func_instance_id_generate(IUDEVICE* pdev, char* strInstanceId, size_t len)
+{
+ char instanceId[17] = { 0 };
+ sprintf_s(instanceId, sizeof(instanceId), "\\%s", pdev->getPath(pdev));
+ /* format */
+ sprintf_s(strInstanceId, len,
+ "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "-%02" PRIx8 "%02" PRIx8 "-%02" PRIx8
+ "%02" PRIx8 "-%02" PRIx8 "%02" PRIx8 "-%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8
+ "%02" PRIx8 "%02" PRIx8 "",
+ instanceId[0], instanceId[1], instanceId[2], instanceId[3], instanceId[4],
+ instanceId[5], instanceId[6], instanceId[7], instanceId[8], instanceId[9],
+ instanceId[10], instanceId[11], instanceId[12], instanceId[13], instanceId[14],
+ instanceId[15]);
+ return 0;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT urbdrc_process_capability_request(GENERIC_CHANNEL_CALLBACK* callback, wStream* s,
+ UINT32 MessageId)
+{
+ UINT32 InterfaceId = 0;
+ UINT32 Version = 0;
+ UINT32 out_size = 0;
+ wStream* out = NULL;
+
+ if (!callback || !s)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, Version);
+
+ if (Version > RIM_CAPABILITY_VERSION_01)
+ Version = RIM_CAPABILITY_VERSION_01;
+
+ InterfaceId = ((STREAM_ID_NONE << 30) | CAPABILITIES_NEGOTIATOR);
+ out_size = 16;
+ out = Stream_New(NULL, out_size);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write_UINT32(out, InterfaceId); /* interface id */
+ Stream_Write_UINT32(out, MessageId); /* message id */
+ Stream_Write_UINT32(out, Version); /* usb protocol version */
+ Stream_Write_UINT32(out, 0x00000000); /* HRESULT */
+ return stream_write_and_free(callback->plugin, callback->channel, out);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT urbdrc_process_channel_create(GENERIC_CHANNEL_CALLBACK* callback, wStream* s,
+ UINT32 MessageId)
+{
+ UINT32 InterfaceId = 0;
+ UINT32 out_size = 0;
+ UINT32 MajorVersion = 0;
+ UINT32 MinorVersion = 0;
+ UINT32 Capabilities = 0;
+ wStream* out = NULL;
+ URBDRC_PLUGIN* urbdrc = NULL;
+
+ if (!callback || !s || !callback->plugin)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, MajorVersion);
+ Stream_Read_UINT32(s, MinorVersion);
+ Stream_Read_UINT32(s, Capabilities);
+
+ /* Version check, we only support version 1.0 */
+ if ((MajorVersion != 1) || (MinorVersion != 0))
+ {
+ WLog_Print(urbdrc->log, WLOG_WARN,
+ "server supports USB channel version %" PRIu32 ".%" PRIu32);
+ WLog_Print(urbdrc->log, WLOG_WARN, "we only support channel version 1.0");
+ MajorVersion = 1;
+ MinorVersion = 0;
+ }
+
+ InterfaceId = ((STREAM_ID_PROXY << 30) | CLIENT_CHANNEL_NOTIFICATION);
+ out_size = 24;
+ out = Stream_New(NULL, out_size);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write_UINT32(out, InterfaceId); /* interface id */
+ Stream_Write_UINT32(out, MessageId); /* message id */
+ Stream_Write_UINT32(out, CHANNEL_CREATED); /* function id */
+ Stream_Write_UINT32(out, MajorVersion);
+ Stream_Write_UINT32(out, MinorVersion);
+ Stream_Write_UINT32(out, Capabilities); /* capabilities version */
+ return stream_write_and_free(callback->plugin, callback->channel, out);
+}
+
+static UINT urdbrc_send_virtual_channel_add(IWTSPlugin* plugin, IWTSVirtualChannel* channel,
+ UINT32 MessageId)
+{
+ const UINT32 InterfaceId = ((STREAM_ID_PROXY << 30) | CLIENT_DEVICE_SINK);
+ wStream* out = Stream_New(NULL, 12);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write_UINT32(out, InterfaceId); /* interface */
+ Stream_Write_UINT32(out, MessageId); /* message id */
+ Stream_Write_UINT32(out, ADD_VIRTUAL_CHANNEL); /* function id */
+ return stream_write_and_free(plugin, channel, out);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT urdbrc_send_usb_device_add(GENERIC_CHANNEL_CALLBACK* callback, IUDEVICE* pdev)
+{
+ wStream* out = NULL;
+ UINT32 InterfaceId = 0;
+ char HardwareIds[2][DEVICE_HARDWARE_ID_SIZE] = { { 0 } };
+ char CompatibilityIds[3][DEVICE_COMPATIBILITY_ID_SIZE] = { { 0 } };
+ char strContainerId[DEVICE_CONTAINER_STR_SIZE] = { 0 };
+ char strInstanceId[DEVICE_INSTANCE_STR_SIZE] = { 0 };
+ const char* composite_str = "USB\\COMPOSITE";
+ const size_t composite_len = 13;
+ size_t size = 0;
+ size_t CompatibilityIdLen[3];
+ size_t HardwareIdsLen[2];
+ size_t ContainerIdLen = 0;
+ size_t InstanceIdLen = 0;
+ size_t cchCompatIds = 0;
+ UINT32 bcdUSB = 0;
+ InterfaceId = ((STREAM_ID_PROXY << 30) | CLIENT_DEVICE_SINK);
+ /* USB kernel driver detach!! */
+ pdev->detach_kernel_driver(pdev);
+ {
+ const UINT16 idVendor = (UINT16)pdev->query_device_descriptor(pdev, ID_VENDOR);
+ const UINT16 idProduct = (UINT16)pdev->query_device_descriptor(pdev, ID_PRODUCT);
+ const UINT16 bcdDevice = (UINT16)pdev->query_device_descriptor(pdev, BCD_DEVICE);
+ sprintf_s(HardwareIds[1], DEVICE_HARDWARE_ID_SIZE,
+ "USB\\VID_%04" PRIX16 "&PID_%04" PRIX16 "", idVendor, idProduct);
+ sprintf_s(HardwareIds[0], DEVICE_HARDWARE_ID_SIZE,
+ "USB\\VID_%04" PRIX16 "&PID_%04" PRIX16 "&REV_%04" PRIX16 "", idVendor, idProduct,
+ bcdDevice);
+ }
+ {
+ const UINT8 bDeviceClass = (UINT8)pdev->query_device_descriptor(pdev, B_DEVICE_CLASS);
+ const UINT8 bDeviceSubClass = (UINT8)pdev->query_device_descriptor(pdev, B_DEVICE_SUBCLASS);
+ const UINT8 bDeviceProtocol = (UINT8)pdev->query_device_descriptor(pdev, B_DEVICE_PROTOCOL);
+
+ if (!(pdev->isCompositeDevice(pdev)))
+ {
+ sprintf_s(CompatibilityIds[2], DEVICE_COMPATIBILITY_ID_SIZE, "USB\\Class_%02" PRIX8 "",
+ bDeviceClass);
+ sprintf_s(CompatibilityIds[1], DEVICE_COMPATIBILITY_ID_SIZE,
+ "USB\\Class_%02" PRIX8 "&SubClass_%02" PRIX8 "", bDeviceClass,
+ bDeviceSubClass);
+ sprintf_s(CompatibilityIds[0], DEVICE_COMPATIBILITY_ID_SIZE,
+ "USB\\Class_%02" PRIX8 "&SubClass_%02" PRIX8 "&Prot_%02" PRIX8 "",
+ bDeviceClass, bDeviceSubClass, bDeviceProtocol);
+ }
+ else
+ {
+ sprintf_s(CompatibilityIds[2], DEVICE_COMPATIBILITY_ID_SIZE, "USB\\DevClass_00");
+ sprintf_s(CompatibilityIds[1], DEVICE_COMPATIBILITY_ID_SIZE,
+ "USB\\DevClass_00&SubClass_00");
+ sprintf_s(CompatibilityIds[0], DEVICE_COMPATIBILITY_ID_SIZE,
+ "USB\\DevClass_00&SubClass_00&Prot_00");
+ }
+ }
+ func_instance_id_generate(pdev, strInstanceId, DEVICE_INSTANCE_STR_SIZE);
+ func_container_id_generate(pdev, strContainerId);
+ CompatibilityIdLen[0] = strnlen(CompatibilityIds[0], sizeof(CompatibilityIds[0]));
+ CompatibilityIdLen[1] = strnlen(CompatibilityIds[1], sizeof(CompatibilityIds[1]));
+ CompatibilityIdLen[2] = strnlen(CompatibilityIds[2], sizeof(CompatibilityIds[2]));
+ HardwareIdsLen[0] = strnlen(HardwareIds[0], sizeof(HardwareIds[0]));
+ HardwareIdsLen[1] = strnlen(HardwareIds[1], sizeof(HardwareIds[1]));
+ cchCompatIds =
+ CompatibilityIdLen[0] + 1 + CompatibilityIdLen[1] + 1 + CompatibilityIdLen[2] + 2;
+ InstanceIdLen = strnlen(strInstanceId, sizeof(strInstanceId));
+ ContainerIdLen = strnlen(strContainerId, sizeof(strContainerId));
+
+ if (pdev->isCompositeDevice(pdev))
+ cchCompatIds += composite_len + 1;
+
+ size = 24;
+ size += (InstanceIdLen + 1) * 2 + (HardwareIdsLen[0] + 1) * 2 + 4 +
+ (HardwareIdsLen[1] + 1) * 2 + 2 + 4 + (cchCompatIds)*2 + (ContainerIdLen + 1) * 2 + 4 +
+ 28;
+ out = Stream_New(NULL, size);
+
+ if (!out)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Write_UINT32(out, InterfaceId); /* interface */
+ Stream_Write_UINT32(out, 0);
+ Stream_Write_UINT32(out, ADD_DEVICE); /* function id */
+ Stream_Write_UINT32(out, 0x00000001); /* NumUsbDevice */
+ Stream_Write_UINT32(out, pdev->get_UsbDevice(pdev)); /* UsbDevice */
+ Stream_Write_UINT32(out, (UINT32)InstanceIdLen + 1); /* cchDeviceInstanceId */
+ if (Stream_Write_UTF16_String_From_UTF8(out, InstanceIdLen, strInstanceId, InstanceIdLen,
+ TRUE) < 0)
+ goto fail;
+ Stream_Write_UINT16(out, 0);
+ Stream_Write_UINT32(out, HardwareIdsLen[0] + HardwareIdsLen[1] + 3); /* cchHwIds */
+ /* HardwareIds 1 */
+ if (Stream_Write_UTF16_String_From_UTF8(out, HardwareIdsLen[0], HardwareIds[0],
+ HardwareIdsLen[0], TRUE) < 0)
+ goto fail;
+ Stream_Write_UINT16(out, 0);
+ if (Stream_Write_UTF16_String_From_UTF8(out, HardwareIdsLen[1], HardwareIds[1],
+ HardwareIdsLen[1], TRUE) < 0)
+ goto fail;
+ Stream_Write_UINT16(out, 0);
+ Stream_Write_UINT16(out, 0); /* add "\0" */
+ Stream_Write_UINT32(out, (UINT32)cchCompatIds); /* cchCompatIds */
+ /* CompatibilityIds */
+ if (Stream_Write_UTF16_String_From_UTF8(out, CompatibilityIdLen[0], CompatibilityIds[0],
+ CompatibilityIdLen[0], TRUE) < 0)
+ goto fail;
+ Stream_Write_UINT16(out, 0);
+ if (Stream_Write_UTF16_String_From_UTF8(out, CompatibilityIdLen[1], CompatibilityIds[1],
+ CompatibilityIdLen[1], TRUE) < 0)
+ goto fail;
+ Stream_Write_UINT16(out, 0);
+ if (Stream_Write_UTF16_String_From_UTF8(out, CompatibilityIdLen[2], CompatibilityIds[2],
+ CompatibilityIdLen[2], TRUE) < 0)
+ goto fail;
+ Stream_Write_UINT16(out, 0);
+
+ if (pdev->isCompositeDevice(pdev))
+ {
+ if (Stream_Write_UTF16_String_From_UTF8(out, composite_len, composite_str, composite_len,
+ TRUE) < 0)
+ goto fail;
+ Stream_Write_UINT16(out, 0);
+ }
+
+ Stream_Write_UINT16(out, 0x0000); /* add "\0" */
+ Stream_Write_UINT32(out, (UINT32)ContainerIdLen + 1); /* cchContainerId */
+ /* ContainerId */
+ if (Stream_Write_UTF16_String_From_UTF8(out, ContainerIdLen, strContainerId, ContainerIdLen,
+ TRUE) < 0)
+ goto fail;
+ Stream_Write_UINT16(out, 0);
+ /* USB_DEVICE_CAPABILITIES 28 bytes */
+ Stream_Write_UINT32(out, 0x0000001c); /* CbSize */
+ Stream_Write_UINT32(out, 2); /* UsbBusInterfaceVersion, 0 ,1 or 2 */ // TODO: Get from libusb
+ Stream_Write_UINT32(out, 0x600); /* USBDI_Version, 0x500 or 0x600 */ // TODO: Get from libusb
+ /* Supported_USB_Version, 0x110,0x110 or 0x200(usb2.0) */
+ bcdUSB = pdev->query_device_descriptor(pdev, BCD_USB);
+ Stream_Write_UINT32(out, bcdUSB);
+ Stream_Write_UINT32(out, 0x00000000); /* HcdCapabilities, MUST always be zero */
+
+ if (bcdUSB < 0x200)
+ Stream_Write_UINT32(out, 0x00000000); /* DeviceIsHighSpeed */
+ else
+ Stream_Write_UINT32(out, 0x00000001); /* DeviceIsHighSpeed */
+
+ Stream_Write_UINT32(out, 0x50); /* NoAckIsochWriteJitterBufferSizeInMs, >=10 or <=512 */
+ return stream_write_and_free(callback->plugin, callback->channel, out);
+
+fail:
+ Stream_Free(out, TRUE);
+ return ERROR_INTERNAL_ERROR;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT urbdrc_exchange_capabilities(GENERIC_CHANNEL_CALLBACK* callback, wStream* data)
+{
+ UINT32 MessageId = 0;
+ UINT32 FunctionId = 0;
+ UINT32 InterfaceId = 0;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!data)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, data, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Rewind_UINT32(data);
+ Stream_Read_UINT32(data, InterfaceId);
+ Stream_Read_UINT32(data, MessageId);
+ Stream_Read_UINT32(data, FunctionId);
+
+ switch (FunctionId)
+ {
+ case RIM_EXCHANGE_CAPABILITY_REQUEST:
+ error = urbdrc_process_capability_request(callback, data, MessageId);
+ break;
+
+ case RIMCALL_RELEASE:
+ break;
+
+ default:
+ error = ERROR_NOT_FOUND;
+ break;
+ }
+
+ return error;
+}
+
+static BOOL urbdrc_announce_devices(IUDEVMAN* udevman)
+{
+ UINT error = ERROR_SUCCESS;
+
+ udevman->loading_lock(udevman);
+ udevman->rewind(udevman);
+
+ while (udevman->has_next(udevman))
+ {
+ IUDEVICE* pdev = udevman->get_next(udevman);
+
+ if (!pdev->isAlreadySend(pdev))
+ {
+ const UINT32 deviceId = pdev->get_UsbDevice(pdev);
+ UINT cerror =
+ urdbrc_send_virtual_channel_add(udevman->plugin, get_channel(udevman), deviceId);
+
+ if (cerror != ERROR_SUCCESS)
+ break;
+ }
+ }
+
+ udevman->loading_unlock(udevman);
+
+ return error == ERROR_SUCCESS;
+}
+
+static UINT urbdrc_device_control_channel(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
+{
+ URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+ IUDEVMAN* udevman = urbdrc->udevman;
+ IWTSVirtualChannel* channel = callback->channel;
+ IUDEVICE* pdev = NULL;
+ BOOL found = FALSE;
+ UINT error = ERROR_INTERNAL_ERROR;
+ UINT32 channelId = callback->channel_mgr->GetChannelId(channel);
+
+ switch (urbdrc->vchannel_status)
+ {
+ case INIT_CHANNEL_IN:
+ /* Control channel was established */
+ error = ERROR_SUCCESS;
+ udevman->initialize(udevman, channelId);
+
+ if (!urbdrc_announce_devices(udevman))
+ goto fail;
+
+ urbdrc->vchannel_status = INIT_CHANNEL_OUT;
+ break;
+
+ case INIT_CHANNEL_OUT:
+ /* A new device channel was created, add the channel
+ * to the device */
+ udevman->loading_lock(udevman);
+ udevman->rewind(udevman);
+
+ while (udevman->has_next(udevman))
+ {
+ pdev = udevman->get_next(udevman);
+
+ if (!pdev->isAlreadySend(pdev))
+ {
+ const UINT32 channelID = callback->channel_mgr->GetChannelId(channel);
+ found = TRUE;
+ pdev->setAlreadySend(pdev);
+ pdev->set_channelManager(pdev, callback->channel_mgr);
+ pdev->set_channelID(pdev, channelID);
+ break;
+ }
+ }
+
+ udevman->loading_unlock(udevman);
+ error = ERROR_SUCCESS;
+
+ if (found && pdev->isAlreadySend(pdev))
+ error = urdbrc_send_usb_device_add(callback, pdev);
+
+ break;
+
+ default:
+ WLog_Print(urbdrc->log, WLOG_ERROR, "vchannel_status unknown value %" PRIu32 "",
+ urbdrc->vchannel_status);
+ break;
+ }
+
+fail:
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT urbdrc_process_channel_notification(GENERIC_CHANNEL_CALLBACK* callback, wStream* data)
+{
+ UINT32 MessageId = 0;
+ UINT32 FunctionId = 0;
+ UINT32 InterfaceId = 0;
+ UINT error = CHANNEL_RC_OK;
+ URBDRC_PLUGIN* urbdrc = NULL;
+
+ if (!callback || !data)
+ return ERROR_INVALID_PARAMETER;
+
+ urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+
+ if (!urbdrc)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, data, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Rewind(data, 4);
+ Stream_Read_UINT32(data, InterfaceId);
+ Stream_Read_UINT32(data, MessageId);
+ Stream_Read_UINT32(data, FunctionId);
+ WLog_Print(urbdrc->log, WLOG_TRACE, "%s [%" PRIu32 "]",
+ call_to_string(FALSE, InterfaceId, FunctionId), FunctionId);
+
+ switch (FunctionId)
+ {
+ case CHANNEL_CREATED:
+ error = urbdrc_process_channel_create(callback, data, MessageId);
+ break;
+
+ case RIMCALL_RELEASE:
+ error = urbdrc_device_control_channel(callback, data);
+ break;
+
+ default:
+ WLog_Print(urbdrc->log, WLOG_TRACE, "unknown FunctionId 0x%" PRIX32 "", FunctionId);
+ error = 1;
+ break;
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT urbdrc_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ IUDEVMAN* udevman = NULL;
+ UINT32 InterfaceId = 0;
+ UINT error = ERROR_INTERNAL_ERROR;
+
+ if (callback == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ if (callback->plugin == NULL)
+ return error;
+
+ urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+
+ if (urbdrc->udevman == NULL)
+ return error;
+
+ udevman = (IUDEVMAN*)urbdrc->udevman;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, data, 12))
+ return ERROR_INVALID_DATA;
+
+ urbdrc_dump_message(urbdrc->log, FALSE, FALSE, data);
+ Stream_Read_UINT32(data, InterfaceId);
+
+ /* Need to check InterfaceId and mask values */
+ switch (InterfaceId)
+ {
+ case CAPABILITIES_NEGOTIATOR | (STREAM_ID_NONE << 30):
+ error = urbdrc_exchange_capabilities(callback, data);
+ break;
+
+ case SERVER_CHANNEL_NOTIFICATION | (STREAM_ID_PROXY << 30):
+ error = urbdrc_process_channel_notification(callback, data);
+ break;
+
+ default:
+ error = urbdrc_process_udev_data_transfer(callback, urbdrc, udevman, data);
+ WLog_DBG(TAG, "urbdrc_process_udev_data_transfer returned 0x%08" PRIx32, error);
+ error = ERROR_SUCCESS; /* Ignore errors, the device may have been unplugged. */
+ break;
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT urbdrc_on_close(IWTSVirtualChannelCallback* pChannelCallback)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+ if (callback)
+ {
+ URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)callback->plugin;
+ if (urbdrc)
+ {
+ IUDEVMAN* udevman = urbdrc->udevman;
+ if (udevman && callback->channel_mgr)
+ {
+ UINT32 control = callback->channel_mgr->GetChannelId(callback->channel);
+ if (udevman->controlChannelId == control)
+ udevman->status |= URBDRC_DEVICE_CHANNEL_CLOSED;
+ else
+ { /* Need to notify the local backend the device is gone */
+ IUDEVICE* pdev = udevman->get_udevice_by_ChannelID(udevman, control);
+ if (pdev)
+ pdev->markChannelClosed(pdev);
+ }
+ }
+ }
+ }
+ free(callback);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT urbdrc_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
+ IWTSVirtualChannel* pChannel, BYTE* pData,
+ BOOL* pbAccept,
+ IWTSVirtualChannelCallback** ppCallback)
+{
+ GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback;
+ GENERIC_CHANNEL_CALLBACK* callback = NULL;
+
+ if (!ppCallback)
+ return ERROR_INVALID_PARAMETER;
+
+ callback = (GENERIC_CHANNEL_CALLBACK*)calloc(1, sizeof(GENERIC_CHANNEL_CALLBACK));
+
+ if (!callback)
+ return ERROR_OUTOFMEMORY;
+
+ callback->iface.OnDataReceived = urbdrc_on_data_received;
+ callback->iface.OnClose = urbdrc_on_close;
+ callback->plugin = listener_callback->plugin;
+ callback->channel_mgr = listener_callback->channel_mgr;
+ callback->channel = pChannel;
+ *ppCallback = (IWTSVirtualChannelCallback*)callback;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT urbdrc_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr)
+{
+ UINT status = 0;
+ URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)pPlugin;
+ IUDEVMAN* udevman = NULL;
+ char channelName[sizeof(URBDRC_CHANNEL_NAME)] = { URBDRC_CHANNEL_NAME };
+
+ if (!urbdrc || !urbdrc->udevman)
+ return ERROR_INVALID_PARAMETER;
+
+ if (urbdrc->initialized)
+ {
+ WLog_ERR(TAG, "[%s] channel initialized twice, aborting", URBDRC_CHANNEL_NAME);
+ return ERROR_INVALID_DATA;
+ }
+ udevman = urbdrc->udevman;
+ urbdrc->listener_callback =
+ (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK));
+
+ if (!urbdrc->listener_callback)
+ return CHANNEL_RC_NO_MEMORY;
+
+ urbdrc->listener_callback->iface.OnNewChannelConnection = urbdrc_on_new_channel_connection;
+ urbdrc->listener_callback->plugin = pPlugin;
+ urbdrc->listener_callback->channel_mgr = pChannelMgr;
+
+ /* [MS-RDPEUSB] 2.1 Transport defines the channel name in uppercase letters */
+ CharUpperA(channelName);
+ status = pChannelMgr->CreateListener(pChannelMgr, channelName, 0,
+ &urbdrc->listener_callback->iface, &urbdrc->listener);
+ if (status != CHANNEL_RC_OK)
+ return status;
+
+ status = CHANNEL_RC_OK;
+ if (udevman->listener_created_callback)
+ status = udevman->listener_created_callback(udevman);
+
+ urbdrc->initialized = status == CHANNEL_RC_OK;
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT urbdrc_plugin_terminated(IWTSPlugin* pPlugin)
+{
+ URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)pPlugin;
+ IUDEVMAN* udevman = NULL;
+
+ if (!urbdrc)
+ return ERROR_INVALID_DATA;
+ if (urbdrc->listener_callback)
+ {
+ IWTSVirtualChannelManager* mgr = urbdrc->listener_callback->channel_mgr;
+ if (mgr)
+ IFCALL(mgr->DestroyListener, mgr, urbdrc->listener);
+ }
+ udevman = urbdrc->udevman;
+
+ if (udevman)
+ {
+ udevman->free(udevman);
+ udevman = NULL;
+ }
+
+ free(urbdrc->subsystem);
+ free(urbdrc->listener_callback);
+ free(urbdrc);
+ return CHANNEL_RC_OK;
+}
+
+static BOOL urbdrc_register_udevman_addin(IWTSPlugin* pPlugin, IUDEVMAN* udevman)
+{
+ URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)pPlugin;
+
+ if (urbdrc->udevman)
+ {
+ WLog_Print(urbdrc->log, WLOG_ERROR, "existing device, abort.");
+ return FALSE;
+ }
+
+ DEBUG_DVC("device registered.");
+ urbdrc->udevman = udevman;
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT urbdrc_load_udevman_addin(IWTSPlugin* pPlugin, LPCSTR name, const ADDIN_ARGV* args)
+{
+ URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)pPlugin;
+ PFREERDP_URBDRC_DEVICE_ENTRY entry = NULL;
+ FREERDP_URBDRC_SERVICE_ENTRY_POINTS entryPoints;
+ entry = (PFREERDP_URBDRC_DEVICE_ENTRY)freerdp_load_channel_addin_entry(URBDRC_CHANNEL_NAME,
+ name, NULL, 0);
+
+ if (!entry)
+ return ERROR_INVALID_OPERATION;
+
+ entryPoints.plugin = pPlugin;
+ entryPoints.pRegisterUDEVMAN = urbdrc_register_udevman_addin;
+ entryPoints.args = args;
+
+ if (entry(&entryPoints) != 0)
+ {
+ WLog_Print(urbdrc->log, WLOG_ERROR, "%s entry returns error.", name);
+ return ERROR_INVALID_OPERATION;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static BOOL urbdrc_set_subsystem(URBDRC_PLUGIN* urbdrc, const char* subsystem)
+{
+ free(urbdrc->subsystem);
+ urbdrc->subsystem = _strdup(subsystem);
+ return (urbdrc->subsystem != NULL);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT urbdrc_process_addin_args(URBDRC_PLUGIN* urbdrc, const ADDIN_ARGV* args)
+{
+ int status = 0;
+ COMMAND_LINE_ARGUMENT_A urbdrc_args[] = {
+ { "dbg", COMMAND_LINE_VALUE_FLAG, "", NULL, BoolValueFalse, -1, NULL, "debug" },
+ { "sys", COMMAND_LINE_VALUE_REQUIRED, "<subsystem>", NULL, NULL, -1, NULL, "subsystem" },
+ { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device list>", NULL, NULL, -1, NULL, "devices" },
+ { "encode", COMMAND_LINE_VALUE_FLAG, "", NULL, NULL, -1, NULL, "encode" },
+ { "quality", COMMAND_LINE_VALUE_REQUIRED, "<[0-2] -> [high-medium-low]>", NULL, NULL, -1,
+ NULL, "quality" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
+ };
+
+ const DWORD flags =
+ COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ status =
+ CommandLineParseArgumentsA(args->argc, args->argv, urbdrc_args, flags, urbdrc, NULL, NULL);
+
+ if (status < 0)
+ return ERROR_INVALID_DATA;
+
+ arg = urbdrc_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dbg")
+ {
+ WLog_SetLogLevel(urbdrc->log, WLOG_TRACE);
+ }
+ CommandLineSwitchCase(arg, "sys")
+ {
+ if (!urbdrc_set_subsystem(urbdrc, arg->Value))
+ return ERROR_OUTOFMEMORY;
+ }
+ CommandLineSwitchDefault(arg)
+ {
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+BOOL add_device(IUDEVMAN* idevman, UINT32 flags, BYTE busnum, BYTE devnum, UINT16 idVendor,
+ UINT16 idProduct)
+{
+ size_t success = 0;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ UINT32 mask = 0;
+ UINT32 regflags = 0;
+
+ if (!idevman)
+ return FALSE;
+
+ urbdrc = (URBDRC_PLUGIN*)idevman->plugin;
+
+ if (!urbdrc || !urbdrc->listener_callback)
+ return FALSE;
+
+ mask = (DEVICE_ADD_FLAG_VENDOR | DEVICE_ADD_FLAG_PRODUCT);
+ if ((flags & mask) == mask)
+ regflags |= UDEVMAN_FLAG_ADD_BY_VID_PID;
+ mask = (DEVICE_ADD_FLAG_BUS | DEVICE_ADD_FLAG_DEV);
+ if ((flags & mask) == mask)
+ regflags |= UDEVMAN_FLAG_ADD_BY_ADDR;
+
+ success = idevman->register_udevice(idevman, busnum, devnum, idVendor, idProduct, regflags);
+
+ if ((success > 0) && (flags & DEVICE_ADD_FLAG_REGISTER))
+ {
+ if (!urbdrc_announce_devices(idevman))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL del_device(IUDEVMAN* idevman, UINT32 flags, BYTE busnum, BYTE devnum, UINT16 idVendor,
+ UINT16 idProduct)
+{
+ IUDEVICE* pdev = NULL;
+ URBDRC_PLUGIN* urbdrc = NULL;
+
+ if (!idevman)
+ return FALSE;
+
+ urbdrc = (URBDRC_PLUGIN*)idevman->plugin;
+
+ if (!urbdrc || !urbdrc->listener_callback)
+ return FALSE;
+
+ idevman->loading_lock(idevman);
+ idevman->rewind(idevman);
+
+ while (idevman->has_next(idevman))
+ {
+ BOOL match = TRUE;
+ IUDEVICE* dev = idevman->get_next(idevman);
+
+ if ((flags & (DEVICE_ADD_FLAG_BUS | DEVICE_ADD_FLAG_DEV | DEVICE_ADD_FLAG_VENDOR |
+ DEVICE_ADD_FLAG_PRODUCT)) == 0)
+ match = FALSE;
+ if (flags & DEVICE_ADD_FLAG_BUS)
+ {
+ if (dev->get_bus_number(dev) != busnum)
+ match = FALSE;
+ }
+ if (flags & DEVICE_ADD_FLAG_DEV)
+ {
+ if (dev->get_dev_number(dev) != devnum)
+ match = FALSE;
+ }
+ if (flags & DEVICE_ADD_FLAG_VENDOR)
+ {
+ int vid = dev->query_device_descriptor(dev, ID_VENDOR);
+ if (vid != idVendor)
+ match = FALSE;
+ }
+ if (flags & DEVICE_ADD_FLAG_PRODUCT)
+ {
+ int pid = dev->query_device_descriptor(dev, ID_PRODUCT);
+ if (pid != idProduct)
+ match = FALSE;
+ }
+
+ if (match)
+ {
+ pdev = dev;
+ break;
+ }
+ }
+
+ if (pdev)
+ pdev->setChannelClosed(pdev);
+
+ idevman->loading_unlock(idevman);
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT urbdrc_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
+{
+ UINT status = 0;
+ const ADDIN_ARGV* args = NULL;
+ URBDRC_PLUGIN* urbdrc = NULL;
+ urbdrc = (URBDRC_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, URBDRC_CHANNEL_NAME);
+ args = pEntryPoints->GetPluginData(pEntryPoints);
+
+ if (urbdrc == NULL)
+ {
+ urbdrc = (URBDRC_PLUGIN*)calloc(1, sizeof(URBDRC_PLUGIN));
+
+ if (!urbdrc)
+ return CHANNEL_RC_NO_MEMORY;
+
+ urbdrc->iface.Initialize = urbdrc_plugin_initialize;
+ urbdrc->iface.Terminated = urbdrc_plugin_terminated;
+ urbdrc->vchannel_status = INIT_CHANNEL_IN;
+ status = pEntryPoints->RegisterPlugin(pEntryPoints, URBDRC_CHANNEL_NAME, &urbdrc->iface);
+
+ /* After we register the plugin free will be taken care of by dynamic channel */
+ if (status != CHANNEL_RC_OK)
+ {
+ free(urbdrc);
+ goto fail;
+ }
+
+ urbdrc->log = WLog_Get(TAG);
+
+ if (!urbdrc->log)
+ goto fail;
+ }
+
+ status = urbdrc_process_addin_args(urbdrc, args);
+
+ if (status != CHANNEL_RC_OK)
+ goto fail;
+
+ if (!urbdrc->subsystem && !urbdrc_set_subsystem(urbdrc, "libusb"))
+ goto fail;
+
+ return urbdrc_load_udevman_addin(&urbdrc->iface, urbdrc->subsystem, args);
+fail:
+ return status;
+}
+
+UINT stream_write_and_free(IWTSPlugin* plugin, IWTSVirtualChannel* channel, wStream* out)
+{
+ UINT rc = 0;
+ URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)plugin;
+
+ if (!out)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!channel || !out || !urbdrc)
+ {
+ Stream_Free(out, TRUE);
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ if (!channel->Write)
+ {
+ Stream_Free(out, TRUE);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ urbdrc_dump_message(urbdrc->log, TRUE, TRUE, out);
+ rc = channel->Write(channel, Stream_GetPosition(out), Stream_Buffer(out), NULL);
+ Stream_Free(out, TRUE);
+ return rc;
+}
diff --git a/channels/urbdrc/client/urbdrc_main.h b/channels/urbdrc/client/urbdrc_main.h
new file mode 100644
index 0000000..d13ed95
--- /dev/null
+++ b/channels/urbdrc/client/urbdrc_main.h
@@ -0,0 +1,222 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX USB Redirection
+ *
+ * Copyright 2012 Atrust corp.
+ * Copyright 2012 Alfred Liu <alfred.liu@atruscorp.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_URBDRC_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_URBDRC_CLIENT_MAIN_H
+
+#include <winpr/pool.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/client/channels.h>
+
+#define DEVICE_HARDWARE_ID_SIZE 32
+#define DEVICE_COMPATIBILITY_ID_SIZE 36
+#define DEVICE_INSTANCE_STR_SIZE 37
+#define DEVICE_CONTAINER_STR_SIZE 39
+
+#define TAG CHANNELS_TAG("urbdrc.client")
+#ifdef WITH_DEBUG_DVC
+#define DEBUG_DVC(...) WLog_DBG(TAG, __VA_ARGS__)
+#else
+#define DEBUG_DVC(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+typedef struct S_IUDEVICE IUDEVICE;
+typedef struct S_IUDEVMAN IUDEVMAN;
+
+#define BASIC_DEV_STATE_DEFINED(_arg, _type) \
+ _type (*get_##_arg)(IUDEVICE * pdev); \
+ void (*set_##_arg)(IUDEVICE * pdev, _type _arg)
+
+#define BASIC_DEVMAN_STATE_DEFINED(_arg, _type) \
+ _type (*get_##_arg)(IUDEVMAN * udevman); \
+ void (*set_##_arg)(IUDEVMAN * udevman, _type _arg)
+
+typedef struct
+{
+ IWTSPlugin iface;
+
+ GENERIC_LISTENER_CALLBACK* listener_callback;
+
+ IUDEVMAN* udevman;
+ UINT32 vchannel_status;
+ char* subsystem;
+
+ wLog* log;
+ IWTSListener* listener;
+ BOOL initialized;
+} URBDRC_PLUGIN;
+
+typedef BOOL (*PREGISTERURBDRCSERVICE)(IWTSPlugin* plugin, IUDEVMAN* udevman);
+typedef struct
+{
+ IWTSPlugin* plugin;
+ PREGISTERURBDRCSERVICE pRegisterUDEVMAN;
+ const ADDIN_ARGV* args;
+} FREERDP_URBDRC_SERVICE_ENTRY_POINTS;
+typedef FREERDP_URBDRC_SERVICE_ENTRY_POINTS* PFREERDP_URBDRC_SERVICE_ENTRY_POINTS;
+
+typedef int (*PFREERDP_URBDRC_DEVICE_ENTRY)(PFREERDP_URBDRC_SERVICE_ENTRY_POINTS pEntryPoints);
+
+typedef struct
+{
+ GENERIC_CHANNEL_CALLBACK* callback;
+ URBDRC_PLUGIN* urbdrc;
+ IUDEVMAN* udevman;
+ IWTSVirtualChannel* channel;
+ wStream* s;
+} TRANSFER_DATA;
+
+typedef void (*t_isoch_transfer_cb)(IUDEVICE* idev, GENERIC_CHANNEL_CALLBACK* callback,
+ wStream* out, UINT32 InterfaceId, BOOL noAck, UINT32 MessageId,
+ UINT32 RequestId, UINT32 NumberOfPackets, UINT32 status,
+ UINT32 StartFrame, UINT32 ErrorCount, UINT32 OutputBufferSize);
+
+struct S_IUDEVICE
+{
+ /* Transfer */
+ int (*isoch_transfer)(IUDEVICE* idev, GENERIC_CHANNEL_CALLBACK* callback, UINT32 MessageId,
+ UINT32 RequestId, UINT32 EndpointAddress, UINT32 TransferFlags,
+ UINT32 StartFrame, UINT32 ErrorCount, BOOL NoAck,
+ const BYTE* packetDescriptorData, UINT32 NumberOfPackets,
+ UINT32 BufferSize, const BYTE* Buffer, t_isoch_transfer_cb cb,
+ UINT32 Timeout);
+
+ BOOL(*control_transfer)
+ (IUDEVICE* idev, UINT32 RequestId, UINT32 EndpointAddress, UINT32 TransferFlags,
+ BYTE bmRequestType, BYTE Request, UINT16 Value, UINT16 Index, UINT32* UrbdStatus,
+ UINT32* BufferSize, BYTE* Buffer, UINT32 Timeout);
+
+ int (*bulk_or_interrupt_transfer)(IUDEVICE* idev, GENERIC_CHANNEL_CALLBACK* callback,
+ UINT32 MessageId, UINT32 RequestId, UINT32 EndpointAddress,
+ UINT32 TransferFlags, BOOL NoAck, UINT32 BufferSize,
+ const BYTE* data, t_isoch_transfer_cb cb, UINT32 Timeout);
+
+ int (*select_configuration)(IUDEVICE* idev, UINT32 bConfigurationValue);
+
+ int (*select_interface)(IUDEVICE* idev, BYTE InterfaceNumber, BYTE AlternateSetting);
+
+ int (*control_pipe_request)(IUDEVICE* idev, UINT32 RequestId, UINT32 EndpointAddress,
+ UINT32* UsbdStatus, int command);
+
+ UINT32(*control_query_device_text)
+ (IUDEVICE* idev, UINT32 TextType, UINT16 LocaleId, UINT8* BufferSize, BYTE* Buffer);
+
+ int (*os_feature_descriptor_request)(IUDEVICE* idev, UINT32 RequestId, BYTE Recipient,
+ BYTE InterfaceNumber, BYTE Ms_PageIndex,
+ UINT16 Ms_featureDescIndex, UINT32* UsbdStatus,
+ UINT32* BufferSize, BYTE* Buffer, UINT32 Timeout);
+
+ void (*cancel_all_transfer_request)(IUDEVICE* idev);
+
+ int (*cancel_transfer_request)(IUDEVICE* idev, UINT32 RequestId);
+
+ int (*query_device_descriptor)(IUDEVICE* idev, int offset);
+
+ BOOL (*detach_kernel_driver)(IUDEVICE* idev);
+
+ BOOL (*attach_kernel_driver)(IUDEVICE* idev);
+
+ int (*query_device_port_status)(IUDEVICE* idev, UINT32* UsbdStatus, UINT32* BufferSize,
+ BYTE* Buffer);
+
+ MSUSB_CONFIG_DESCRIPTOR* (*complete_msconfig_setup)(IUDEVICE* idev,
+ MSUSB_CONFIG_DESCRIPTOR* MsConfig);
+ /* Basic state */
+ int (*isCompositeDevice)(IUDEVICE* idev);
+
+ int (*isExist)(IUDEVICE* idev);
+ int (*isAlreadySend)(IUDEVICE* idev);
+ int (*isChannelClosed)(IUDEVICE* idev);
+
+ void (*setAlreadySend)(IUDEVICE* idev);
+ void (*setChannelClosed)(IUDEVICE* idev);
+ void (*markChannelClosed)(IUDEVICE* idev);
+ char* (*getPath)(IUDEVICE* idev);
+
+ void (*free)(IUDEVICE* idev);
+
+ BASIC_DEV_STATE_DEFINED(channelManager, IWTSVirtualChannelManager*);
+ BASIC_DEV_STATE_DEFINED(channelID, UINT32);
+ BASIC_DEV_STATE_DEFINED(UsbDevice, UINT32);
+ BASIC_DEV_STATE_DEFINED(ReqCompletion, UINT32);
+ BASIC_DEV_STATE_DEFINED(bus_number, BYTE);
+ BASIC_DEV_STATE_DEFINED(dev_number, BYTE);
+ BASIC_DEV_STATE_DEFINED(port_number, int);
+ BASIC_DEV_STATE_DEFINED(MsConfig, MSUSB_CONFIG_DESCRIPTOR*);
+
+ BASIC_DEV_STATE_DEFINED(p_udev, void*);
+ BASIC_DEV_STATE_DEFINED(p_prev, void*);
+ BASIC_DEV_STATE_DEFINED(p_next, void*);
+};
+
+struct S_IUDEVMAN
+{
+ /* Standard */
+ void (*free)(IUDEVMAN* idevman);
+
+ /* Manage devices */
+ void (*rewind)(IUDEVMAN* idevman);
+ BOOL (*has_next)(IUDEVMAN* idevman);
+ BOOL (*unregister_udevice)(IUDEVMAN* idevman, BYTE bus_number, BYTE dev_number);
+ size_t (*register_udevice)(IUDEVMAN* idevman, BYTE bus_number, BYTE dev_number, UINT16 idVendor,
+ UINT16 idProduct, UINT32 flag);
+ IUDEVICE* (*get_next)(IUDEVMAN* idevman);
+ IUDEVICE* (*get_udevice_by_UsbDevice)(IUDEVMAN* idevman, UINT32 UsbDevice);
+ IUDEVICE* (*get_udevice_by_ChannelID)(IUDEVMAN* idevman, UINT32 channelID);
+
+ /* Extension */
+ int (*isAutoAdd)(IUDEVMAN* idevman);
+
+ /* Basic state */
+ BASIC_DEVMAN_STATE_DEFINED(device_num, UINT32);
+ BASIC_DEVMAN_STATE_DEFINED(next_device_id, UINT32);
+
+ /* control semaphore or mutex lock */
+ void (*loading_lock)(IUDEVMAN* idevman);
+ void (*loading_unlock)(IUDEVMAN* idevman);
+ BOOL (*initialize)(IUDEVMAN* idevman, UINT32 channelId);
+ UINT (*listener_created_callback)(IUDEVMAN* idevman);
+
+ IWTSPlugin* plugin;
+ UINT32 controlChannelId;
+ UINT32 status;
+};
+
+#define DEVICE_ADD_FLAG_BUS 0x01
+#define DEVICE_ADD_FLAG_DEV 0x02
+#define DEVICE_ADD_FLAG_VENDOR 0x04
+#define DEVICE_ADD_FLAG_PRODUCT 0x08
+#define DEVICE_ADD_FLAG_REGISTER 0x10
+
+#define DEVICE_ADD_FLAG_ALL \
+ (DEVICE_ADD_FLAG_BUS | DEVICE_ADD_FLAG_DEV | DEVICE_ADD_FLAG_VENDOR | \
+ DEVICE_ADD_FLAG_PRODUCT | DEVICE_ADD_FLAG_REGISTER)
+
+FREERDP_API BOOL add_device(IUDEVMAN* idevman, UINT32 flags, BYTE busnum, BYTE devnum,
+ UINT16 idVendor, UINT16 idProduct);
+FREERDP_API BOOL del_device(IUDEVMAN* idevman, UINT32 flags, BYTE busnum, BYTE devnum,
+ UINT16 idVendor, UINT16 idProduct);
+
+UINT stream_write_and_free(IWTSPlugin* plugin, IWTSVirtualChannel* channel, wStream* s);
+
+#endif /* FREERDP_CHANNEL_URBDRC_CLIENT_MAIN_H */
diff --git a/channels/urbdrc/common/CMakeLists.txt b/channels/urbdrc/common/CMakeLists.txt
new file mode 100644
index 0000000..df9a8a9
--- /dev/null
+++ b/channels/urbdrc/common/CMakeLists.txt
@@ -0,0 +1,29 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2019 Armin Novak <armin.novak@thincast.com>
+# Copyright 2019 Thincast Technologies GmbH
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set(SRCS
+ urbdrc_types.h
+ urbdrc_helpers.h
+ urbdrc_helpers.c
+ msusb.h
+ msusb.c)
+
+add_library(urbdrc-common STATIC ${SRCS})
+
+channel_install(urbdrc-common ${FREERDP_ADDIN_PATH} "FreeRDPTargets")
+
diff --git a/channels/urbdrc/common/msusb.c b/channels/urbdrc/common/msusb.c
new file mode 100644
index 0000000..dd76b1d
--- /dev/null
+++ b/channels/urbdrc/common/msusb.c
@@ -0,0 +1,395 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX USB Redirection
+ *
+ * Copyright 2012 Atrust corp.
+ * Copyright 2012 Alfred Liu <alfred.liu@atruscorp.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <freerdp/log.h>
+#include <msusb.h>
+
+#define TAG FREERDP_TAG("utils")
+
+static MSUSB_PIPE_DESCRIPTOR* msusb_mspipe_new(void)
+{
+ return (MSUSB_PIPE_DESCRIPTOR*)calloc(1, sizeof(MSUSB_PIPE_DESCRIPTOR));
+}
+
+static void msusb_mspipes_free(MSUSB_PIPE_DESCRIPTOR** MsPipes, UINT32 NumberOfPipes)
+{
+ if (MsPipes)
+ {
+ for (UINT32 pnum = 0; pnum < NumberOfPipes && MsPipes[pnum]; pnum++)
+ free(MsPipes[pnum]);
+
+ free(MsPipes);
+ }
+}
+
+BOOL msusb_mspipes_replace(MSUSB_INTERFACE_DESCRIPTOR* MsInterface,
+ MSUSB_PIPE_DESCRIPTOR** NewMsPipes, UINT32 NewNumberOfPipes)
+{
+ if (!MsInterface || !NewMsPipes)
+ return FALSE;
+
+ /* free orignal MsPipes */
+ msusb_mspipes_free(MsInterface->MsPipes, MsInterface->NumberOfPipes);
+ /* And replace it */
+ MsInterface->MsPipes = NewMsPipes;
+ MsInterface->NumberOfPipes = NewNumberOfPipes;
+ return TRUE;
+}
+
+static MSUSB_PIPE_DESCRIPTOR** msusb_mspipes_read(wStream* s, UINT32 NumberOfPipes)
+{
+ MSUSB_PIPE_DESCRIPTOR** MsPipes = NULL;
+
+ if (!Stream_CheckAndLogRequiredCapacityOfSize(TAG, (s), NumberOfPipes, 12ull))
+ return NULL;
+
+ MsPipes = (MSUSB_PIPE_DESCRIPTOR**)calloc(NumberOfPipes, sizeof(MSUSB_PIPE_DESCRIPTOR*));
+
+ if (!MsPipes)
+ return NULL;
+
+ for (UINT32 pnum = 0; pnum < NumberOfPipes; pnum++)
+ {
+ MSUSB_PIPE_DESCRIPTOR* MsPipe = msusb_mspipe_new();
+
+ if (!MsPipe)
+ goto out_error;
+
+ Stream_Read_UINT16(s, MsPipe->MaximumPacketSize);
+ Stream_Seek(s, 2);
+ Stream_Read_UINT32(s, MsPipe->MaximumTransferSize);
+ Stream_Read_UINT32(s, MsPipe->PipeFlags);
+ /* Already set to zero by memset
+ MsPipe->PipeHandle = 0;
+ MsPipe->bEndpointAddress = 0;
+ MsPipe->bInterval = 0;
+ MsPipe->PipeType = 0;
+ MsPipe->InitCompleted = 0;
+ */
+ MsPipes[pnum] = MsPipe;
+ }
+
+ return MsPipes;
+out_error:
+
+ for (UINT32 pnum = 0; pnum < NumberOfPipes; pnum++)
+ free(MsPipes[pnum]);
+
+ free(MsPipes);
+ return NULL;
+}
+
+static MSUSB_INTERFACE_DESCRIPTOR* msusb_msinterface_new(void)
+{
+ return (MSUSB_INTERFACE_DESCRIPTOR*)calloc(1, sizeof(MSUSB_INTERFACE_DESCRIPTOR));
+}
+
+void msusb_msinterface_free(MSUSB_INTERFACE_DESCRIPTOR* MsInterface)
+{
+ if (MsInterface)
+ {
+ msusb_mspipes_free(MsInterface->MsPipes, MsInterface->NumberOfPipes);
+ MsInterface->MsPipes = NULL;
+ free(MsInterface);
+ }
+}
+
+static void msusb_msinterface_free_list(MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces,
+ UINT32 NumInterfaces)
+{
+ if (MsInterfaces)
+ {
+ for (UINT32 inum = 0; inum < NumInterfaces; inum++)
+ {
+ msusb_msinterface_free(MsInterfaces[inum]);
+ }
+
+ free(MsInterfaces);
+ }
+}
+
+BOOL msusb_msinterface_replace(MSUSB_CONFIG_DESCRIPTOR* MsConfig, BYTE InterfaceNumber,
+ MSUSB_INTERFACE_DESCRIPTOR* NewMsInterface)
+{
+ if (!MsConfig || !MsConfig->MsInterfaces)
+ return FALSE;
+
+ msusb_msinterface_free(MsConfig->MsInterfaces[InterfaceNumber]);
+ MsConfig->MsInterfaces[InterfaceNumber] = NewMsInterface;
+ return TRUE;
+}
+
+MSUSB_INTERFACE_DESCRIPTOR* msusb_msinterface_read(wStream* s)
+{
+ MSUSB_INTERFACE_DESCRIPTOR* MsInterface = NULL;
+
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, (s), 12))
+ return NULL;
+
+ MsInterface = msusb_msinterface_new();
+
+ if (!MsInterface)
+ return NULL;
+
+ Stream_Read_UINT16(s, MsInterface->Length);
+ Stream_Read_UINT16(s, MsInterface->NumberOfPipesExpected);
+ Stream_Read_UINT8(s, MsInterface->InterfaceNumber);
+ Stream_Read_UINT8(s, MsInterface->AlternateSetting);
+ Stream_Seek(s, 2);
+ Stream_Read_UINT32(s, MsInterface->NumberOfPipes);
+ MsInterface->InterfaceHandle = 0;
+ MsInterface->bInterfaceClass = 0;
+ MsInterface->bInterfaceSubClass = 0;
+ MsInterface->bInterfaceProtocol = 0;
+ MsInterface->InitCompleted = 0;
+ MsInterface->MsPipes = NULL;
+
+ if (MsInterface->NumberOfPipes > 0)
+ {
+ MsInterface->MsPipes = msusb_mspipes_read(s, MsInterface->NumberOfPipes);
+
+ if (!MsInterface->MsPipes)
+ goto out_error;
+ }
+
+ return MsInterface;
+out_error:
+ msusb_msinterface_free(MsInterface);
+ return NULL;
+}
+
+BOOL msusb_msinterface_write(MSUSB_INTERFACE_DESCRIPTOR* MsInterface, wStream* out)
+{
+ MSUSB_PIPE_DESCRIPTOR** MsPipes = NULL;
+ MSUSB_PIPE_DESCRIPTOR* MsPipe = NULL;
+
+ if (!MsInterface)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(out, 16 + MsInterface->NumberOfPipes * 20))
+ return FALSE;
+
+ /* Length */
+ Stream_Write_UINT16(out, MsInterface->Length);
+ /* InterfaceNumber */
+ Stream_Write_UINT8(out, MsInterface->InterfaceNumber);
+ /* AlternateSetting */
+ Stream_Write_UINT8(out, MsInterface->AlternateSetting);
+ /* bInterfaceClass */
+ Stream_Write_UINT8(out, MsInterface->bInterfaceClass);
+ /* bInterfaceSubClass */
+ Stream_Write_UINT8(out, MsInterface->bInterfaceSubClass);
+ /* bInterfaceProtocol */
+ Stream_Write_UINT8(out, MsInterface->bInterfaceProtocol);
+ /* Padding */
+ Stream_Write_UINT8(out, 0);
+ /* InterfaceHandle */
+ Stream_Write_UINT32(out, MsInterface->InterfaceHandle);
+ /* NumberOfPipes */
+ Stream_Write_UINT32(out, MsInterface->NumberOfPipes);
+ /* Pipes */
+ MsPipes = MsInterface->MsPipes;
+
+ for (UINT32 pnum = 0; pnum < MsInterface->NumberOfPipes; pnum++)
+ {
+ MsPipe = MsPipes[pnum];
+ /* MaximumPacketSize */
+ Stream_Write_UINT16(out, MsPipe->MaximumPacketSize);
+ /* EndpointAddress */
+ Stream_Write_UINT8(out, MsPipe->bEndpointAddress);
+ /* Interval */
+ Stream_Write_UINT8(out, MsPipe->bInterval);
+ /* PipeType */
+ Stream_Write_UINT32(out, MsPipe->PipeType);
+ /* PipeHandle */
+ Stream_Write_UINT32(out, MsPipe->PipeHandle);
+ /* MaximumTransferSize */
+ Stream_Write_UINT32(out, MsPipe->MaximumTransferSize);
+ /* PipeFlags */
+ Stream_Write_UINT32(out, MsPipe->PipeFlags);
+ }
+
+ return TRUE;
+}
+
+static MSUSB_INTERFACE_DESCRIPTOR** msusb_msinterface_read_list(wStream* s, UINT32 NumInterfaces)
+{
+ MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces = NULL;
+ MsInterfaces =
+ (MSUSB_INTERFACE_DESCRIPTOR**)calloc(NumInterfaces, sizeof(MSUSB_INTERFACE_DESCRIPTOR*));
+
+ if (!MsInterfaces)
+ return NULL;
+
+ for (UINT32 inum = 0; inum < NumInterfaces; inum++)
+ {
+ MsInterfaces[inum] = msusb_msinterface_read(s);
+
+ if (!MsInterfaces[inum])
+ goto fail;
+ }
+
+ return MsInterfaces;
+fail:
+
+ for (UINT32 inum = 0; inum < NumInterfaces; inum++)
+ msusb_msinterface_free(MsInterfaces[inum]);
+
+ free(MsInterfaces);
+ return NULL;
+}
+
+BOOL msusb_msconfig_write(MSUSB_CONFIG_DESCRIPTOR* MsConfg, wStream* out)
+{
+ MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces = NULL;
+ MSUSB_INTERFACE_DESCRIPTOR* MsInterface = NULL;
+
+ if (!MsConfg)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(out, 8))
+ return FALSE;
+
+ /* ConfigurationHandle*/
+ Stream_Write_UINT32(out, MsConfg->ConfigurationHandle);
+ /* NumInterfaces*/
+ Stream_Write_UINT32(out, MsConfg->NumInterfaces);
+ /* Interfaces */
+ MsInterfaces = MsConfg->MsInterfaces;
+
+ for (UINT32 inum = 0; inum < MsConfg->NumInterfaces; inum++)
+ {
+ MsInterface = MsInterfaces[inum];
+
+ if (!msusb_msinterface_write(MsInterface, out))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+MSUSB_CONFIG_DESCRIPTOR* msusb_msconfig_new(void)
+{
+ return (MSUSB_CONFIG_DESCRIPTOR*)calloc(1, sizeof(MSUSB_CONFIG_DESCRIPTOR));
+}
+
+void msusb_msconfig_free(MSUSB_CONFIG_DESCRIPTOR* MsConfig)
+{
+ if (MsConfig)
+ {
+ msusb_msinterface_free_list(MsConfig->MsInterfaces, MsConfig->NumInterfaces);
+ MsConfig->MsInterfaces = NULL;
+ free(MsConfig);
+ }
+}
+
+MSUSB_CONFIG_DESCRIPTOR* msusb_msconfig_read(wStream* s, UINT32 NumInterfaces)
+{
+ MSUSB_CONFIG_DESCRIPTOR* MsConfig = NULL;
+ BYTE lenConfiguration = 0;
+ BYTE typeConfiguration = 0;
+
+ if (!Stream_CheckAndLogRequiredCapacityOfSize(TAG, (s), 3ULL + NumInterfaces, 2ULL))
+ return NULL;
+
+ MsConfig = msusb_msconfig_new();
+
+ if (!MsConfig)
+ goto fail;
+
+ MsConfig->MsInterfaces = msusb_msinterface_read_list(s, NumInterfaces);
+
+ if (!MsConfig->MsInterfaces)
+ goto fail;
+
+ Stream_Read_UINT8(s, lenConfiguration);
+ Stream_Read_UINT8(s, typeConfiguration);
+
+ if (lenConfiguration != 0x9 || typeConfiguration != 0x2)
+ {
+ WLog_ERR(TAG, "len and type must be 0x9 and 0x2 , but it is 0x%" PRIx8 " and 0x%" PRIx8 "",
+ lenConfiguration, typeConfiguration);
+ goto fail;
+ }
+
+ Stream_Read_UINT16(s, MsConfig->wTotalLength);
+ Stream_Seek(s, 1);
+ Stream_Read_UINT8(s, MsConfig->bConfigurationValue);
+ MsConfig->NumInterfaces = NumInterfaces;
+ return MsConfig;
+fail:
+ msusb_msconfig_free(MsConfig);
+ return NULL;
+}
+
+void msusb_msconfig_dump(MSUSB_CONFIG_DESCRIPTOR* MsConfig)
+{
+ MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces = NULL;
+ MSUSB_INTERFACE_DESCRIPTOR* MsInterface = NULL;
+ MSUSB_PIPE_DESCRIPTOR** MsPipes = NULL;
+ MSUSB_PIPE_DESCRIPTOR* MsPipe = NULL;
+
+ WLog_INFO(TAG, "=================MsConfig:========================");
+ WLog_INFO(TAG, "wTotalLength:%" PRIu16 "", MsConfig->wTotalLength);
+ WLog_INFO(TAG, "bConfigurationValue:%" PRIu8 "", MsConfig->bConfigurationValue);
+ WLog_INFO(TAG, "ConfigurationHandle:0x%08" PRIx32 "", MsConfig->ConfigurationHandle);
+ WLog_INFO(TAG, "InitCompleted:%d", MsConfig->InitCompleted);
+ WLog_INFO(TAG, "MsOutSize:%d", MsConfig->MsOutSize);
+ WLog_INFO(TAG, "NumInterfaces:%" PRIu32 "", MsConfig->NumInterfaces);
+ MsInterfaces = MsConfig->MsInterfaces;
+
+ for (UINT32 inum = 0; inum < MsConfig->NumInterfaces; inum++)
+ {
+ MsInterface = MsInterfaces[inum];
+ WLog_INFO(TAG, " Interface: %" PRIu8 "", MsInterface->InterfaceNumber);
+ WLog_INFO(TAG, " Length: %" PRIu16 "", MsInterface->Length);
+ WLog_INFO(TAG, " NumberOfPipesExpected: %" PRIu16 "",
+ MsInterface->NumberOfPipesExpected);
+ WLog_INFO(TAG, " AlternateSetting: %" PRIu8 "", MsInterface->AlternateSetting);
+ WLog_INFO(TAG, " NumberOfPipes: %" PRIu32 "", MsInterface->NumberOfPipes);
+ WLog_INFO(TAG, " InterfaceHandle: 0x%08" PRIx32 "", MsInterface->InterfaceHandle);
+ WLog_INFO(TAG, " bInterfaceClass: 0x%02" PRIx8 "", MsInterface->bInterfaceClass);
+ WLog_INFO(TAG, " bInterfaceSubClass: 0x%02" PRIx8 "", MsInterface->bInterfaceSubClass);
+ WLog_INFO(TAG, " bInterfaceProtocol: 0x%02" PRIx8 "", MsInterface->bInterfaceProtocol);
+ WLog_INFO(TAG, " InitCompleted: %d", MsInterface->InitCompleted);
+ MsPipes = MsInterface->MsPipes;
+
+ for (UINT32 pnum = 0; pnum < MsInterface->NumberOfPipes; pnum++)
+ {
+ MsPipe = MsPipes[pnum];
+ WLog_INFO(TAG, " Pipe: %" PRIu32, pnum);
+ WLog_INFO(TAG, " MaximumPacketSize: 0x%04" PRIx16 "", MsPipe->MaximumPacketSize);
+ WLog_INFO(TAG, " MaximumTransferSize: 0x%08" PRIx32 "",
+ MsPipe->MaximumTransferSize);
+ WLog_INFO(TAG, " PipeFlags: 0x%08" PRIx32 "", MsPipe->PipeFlags);
+ WLog_INFO(TAG, " PipeHandle: 0x%08" PRIx32 "", MsPipe->PipeHandle);
+ WLog_INFO(TAG, " bEndpointAddress: 0x%02" PRIx8 "", MsPipe->bEndpointAddress);
+ WLog_INFO(TAG, " bInterval: %" PRIu8 "", MsPipe->bInterval);
+ WLog_INFO(TAG, " PipeType: 0x%02" PRIx8 "", MsPipe->PipeType);
+ WLog_INFO(TAG, " InitCompleted: %d", MsPipe->InitCompleted);
+ }
+ }
+
+ WLog_INFO(TAG, "==================================================");
+}
diff --git a/channels/urbdrc/common/msusb.h b/channels/urbdrc/common/msusb.h
new file mode 100644
index 0000000..6ce843f
--- /dev/null
+++ b/channels/urbdrc/common/msusb.h
@@ -0,0 +1,98 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX USB Redirection
+ *
+ * Copyright 2012 Atrust corp.
+ * Copyright 2012 Alfred Liu <alfred.liu@atruscorp.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_UTILS_MSCONFIG_H
+#define FREERDP_UTILS_MSCONFIG_H
+
+#include <winpr/stream.h>
+#include <freerdp/api.h>
+
+typedef struct
+{
+ UINT16 MaximumPacketSize;
+ UINT32 MaximumTransferSize;
+ UINT32 PipeFlags;
+ UINT32 PipeHandle;
+ BYTE bEndpointAddress;
+ BYTE bInterval;
+ BYTE PipeType;
+ int InitCompleted;
+} MSUSB_PIPE_DESCRIPTOR;
+
+typedef struct
+{
+ UINT16 Length;
+ UINT16 NumberOfPipesExpected;
+ BYTE InterfaceNumber;
+ BYTE AlternateSetting;
+ UINT32 NumberOfPipes;
+ UINT32 InterfaceHandle;
+ BYTE bInterfaceClass;
+ BYTE bInterfaceSubClass;
+ BYTE bInterfaceProtocol;
+ MSUSB_PIPE_DESCRIPTOR** MsPipes;
+ int InitCompleted;
+} MSUSB_INTERFACE_DESCRIPTOR;
+
+typedef struct
+{
+ UINT16 wTotalLength;
+ BYTE bConfigurationValue;
+ UINT32 ConfigurationHandle;
+ UINT32 NumInterfaces;
+ MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces;
+ int InitCompleted;
+ int MsOutSize;
+} MSUSB_CONFIG_DESCRIPTOR;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /* MSUSB_PIPE exported functions */
+ FREERDP_API BOOL msusb_mspipes_replace(MSUSB_INTERFACE_DESCRIPTOR* MsInterface,
+ MSUSB_PIPE_DESCRIPTOR** NewMsPipes,
+ UINT32 NewNumberOfPipes);
+
+ /* MSUSB_INTERFACE exported functions */
+ FREERDP_API BOOL msusb_msinterface_replace(MSUSB_CONFIG_DESCRIPTOR* MsConfig,
+ BYTE InterfaceNumber,
+ MSUSB_INTERFACE_DESCRIPTOR* NewMsInterface);
+ FREERDP_API MSUSB_INTERFACE_DESCRIPTOR* msusb_msinterface_read(wStream* out);
+ FREERDP_API BOOL msusb_msinterface_write(MSUSB_INTERFACE_DESCRIPTOR* MsInterface, wStream* out);
+ FREERDP_API void msusb_msinterface_free(MSUSB_INTERFACE_DESCRIPTOR* MsInterface);
+
+ /* MSUSB_CONFIG exported functions */
+ FREERDP_API void msusb_msconfig_free(MSUSB_CONFIG_DESCRIPTOR* MsConfig);
+
+ WINPR_ATTR_MALLOC(msusb_msconfig_free, 1)
+ FREERDP_API MSUSB_CONFIG_DESCRIPTOR* msusb_msconfig_new(void);
+
+ WINPR_ATTR_MALLOC(msusb_msconfig_free, 1)
+ FREERDP_API MSUSB_CONFIG_DESCRIPTOR* msusb_msconfig_read(wStream* s, UINT32 NumInterfaces);
+ FREERDP_API BOOL msusb_msconfig_write(MSUSB_CONFIG_DESCRIPTOR* MsConfg, wStream* out);
+ FREERDP_API void msusb_msconfig_dump(MSUSB_CONFIG_DESCRIPTOR* MsConfg);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_UTILS_MSCONFIG_H */
diff --git a/channels/urbdrc/common/urbdrc_helpers.c b/channels/urbdrc/common/urbdrc_helpers.c
new file mode 100644
index 0000000..9d6bb24
--- /dev/null
+++ b/channels/urbdrc/common/urbdrc_helpers.c
@@ -0,0 +1,425 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Server USB redirection channel - helper functions
+ *
+ * Copyright 2019 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2019 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <freerdp/config.h>
+
+#include "urbdrc_helpers.h"
+#include "urbdrc_types.h"
+#include <winpr/print.h>
+
+const char* mask_to_string(UINT32 mask)
+{
+ switch (mask)
+ {
+ case STREAM_ID_NONE:
+ return "STREAM_ID_NONE";
+
+ case STREAM_ID_PROXY:
+ return "STREAM_ID_PROXY";
+
+ case STREAM_ID_STUB:
+ return "STREAM_ID_STUB";
+
+ default:
+ return "UNKNOWN";
+ }
+}
+const char* interface_to_string(UINT32 id)
+{
+ switch (id)
+ {
+ case CAPABILITIES_NEGOTIATOR:
+ return "CAPABILITIES_NEGOTIATOR";
+
+ case SERVER_CHANNEL_NOTIFICATION:
+ return "SERVER_CHANNEL_NOTIFICATION";
+
+ case CLIENT_CHANNEL_NOTIFICATION:
+ return "CLIENT_CHANNEL_NOTIFICATION";
+
+ default:
+ return "DEVICE_MESSAGE";
+ }
+}
+
+static const char* call_to_string_none(BOOL client, UINT32 interfaceId, UINT32 functionId)
+{
+ WINPR_UNUSED(interfaceId);
+
+ if (client)
+ return "RIM_EXCHANGE_CAPABILITY_RESPONSE [none |client]";
+ else
+ {
+ switch (functionId)
+ {
+ case RIM_EXCHANGE_CAPABILITY_REQUEST:
+ return "RIM_EXCHANGE_CAPABILITY_REQUEST [none |server]";
+
+ case RIMCALL_RELEASE:
+ return "RIMCALL_RELEASE [none |server]";
+
+ case RIMCALL_QUERYINTERFACE:
+ return "RIMCALL_QUERYINTERFACE [none |server]";
+
+ default:
+ return "UNKNOWN [none |server]";
+ }
+ }
+}
+
+static const char* call_to_string_proxy_server(UINT32 functionId)
+{
+ switch (functionId)
+ {
+ case QUERY_DEVICE_TEXT:
+ return "QUERY_DEVICE_TEXT [proxy|server]";
+
+ case INTERNAL_IO_CONTROL:
+ return "INTERNAL_IO_CONTROL [proxy|server]";
+
+ case IO_CONTROL:
+ return "IO_CONTROL [proxy|server]";
+
+ case REGISTER_REQUEST_CALLBACK:
+ return "REGISTER_REQUEST_CALLBACK [proxy|server]";
+
+ case CANCEL_REQUEST:
+ return "CANCEL_REQUEST [proxy|server]";
+
+ case RETRACT_DEVICE:
+ return "RETRACT_DEVICE [proxy|server]";
+
+ case TRANSFER_IN_REQUEST:
+ return "TRANSFER_IN_REQUEST [proxy|server]";
+
+ case TRANSFER_OUT_REQUEST:
+ return "TRANSFER_OUT_REQUEST [proxy|server]";
+
+ default:
+ return "UNKNOWN [proxy|server]";
+ }
+}
+
+static const char* call_to_string_proxy_client(UINT32 functionId)
+{
+ switch (functionId)
+ {
+ case URB_COMPLETION_NO_DATA:
+ return "URB_COMPLETION_NO_DATA [proxy|client]";
+
+ case URB_COMPLETION:
+ return "URB_COMPLETION [proxy|client]";
+
+ case IOCONTROL_COMPLETION:
+ return "IOCONTROL_COMPLETION [proxy|client]";
+
+ case TRANSFER_OUT_REQUEST:
+ return "TRANSFER_OUT_REQUEST [proxy|client]";
+
+ default:
+ return "UNKNOWN [proxy|client]";
+ }
+}
+
+static const char* call_to_string_proxy(BOOL client, UINT32 interfaceId, UINT32 functionId)
+{
+ switch (interfaceId & INTERFACE_ID_MASK)
+ {
+ case CLIENT_DEVICE_SINK:
+ switch (functionId)
+ {
+ case ADD_VIRTUAL_CHANNEL:
+ return "ADD_VIRTUAL_CHANNEL [proxy|sink ]";
+
+ case ADD_DEVICE:
+ return "ADD_DEVICE [proxy|sink ]";
+ case RIMCALL_RELEASE:
+ return "RIMCALL_RELEASE [proxy|sink ]";
+
+ case RIMCALL_QUERYINTERFACE:
+ return "RIMCALL_QUERYINTERFACE [proxy|sink ]";
+ default:
+ return "UNKNOWN [proxy|sink ]";
+ }
+
+ case SERVER_CHANNEL_NOTIFICATION:
+ switch (functionId)
+ {
+ case CHANNEL_CREATED:
+ return "CHANNEL_CREATED [proxy|server]";
+
+ case RIMCALL_RELEASE:
+ return "RIMCALL_RELEASE [proxy|server]";
+
+ case RIMCALL_QUERYINTERFACE:
+ return "RIMCALL_QUERYINTERFACE [proxy|server]";
+
+ default:
+ return "UNKNOWN [proxy|server]";
+ }
+
+ case CLIENT_CHANNEL_NOTIFICATION:
+ switch (functionId)
+ {
+ case CHANNEL_CREATED:
+ return "CHANNEL_CREATED [proxy|client]";
+ case RIMCALL_RELEASE:
+ return "RIMCALL_RELEASE [proxy|client]";
+ case RIMCALL_QUERYINTERFACE:
+ return "RIMCALL_QUERYINTERFACE [proxy|client]";
+ default:
+ return "UNKNOWN [proxy|client]";
+ }
+
+ default:
+ if (client)
+ return call_to_string_proxy_client(functionId);
+ else
+ return call_to_string_proxy_server(functionId);
+ }
+}
+
+static const char* call_to_string_stub(BOOL client, UINT32 interfaceNr, UINT32 functionId)
+{
+ return "QUERY_DEVICE_TEXT_RSP [stub |client]";
+}
+
+const char* call_to_string(BOOL client, UINT32 interfaceNr, UINT32 functionId)
+{
+ const UINT32 mask = (interfaceNr & STREAM_ID_MASK) >> 30;
+ const UINT32 interfaceId = interfaceNr & INTERFACE_ID_MASK;
+
+ switch (mask)
+ {
+ case STREAM_ID_NONE:
+ return call_to_string_none(client, interfaceId, functionId);
+
+ case STREAM_ID_PROXY:
+ return call_to_string_proxy(client, interfaceId, functionId);
+
+ case STREAM_ID_STUB:
+ return call_to_string_stub(client, interfaceId, functionId);
+
+ default:
+ return "UNKNOWN[mask]";
+ }
+}
+
+const char* urb_function_string(UINT16 urb)
+{
+ switch (urb)
+ {
+ case TS_URB_SELECT_CONFIGURATION:
+ return "TS_URB_SELECT_CONFIGURATION";
+
+ case TS_URB_SELECT_INTERFACE:
+ return "TS_URB_SELECT_INTERFACE";
+
+ case TS_URB_PIPE_REQUEST:
+ return "TS_URB_PIPE_REQUEST";
+
+ case TS_URB_TAKE_FRAME_LENGTH_CONTROL:
+ return "TS_URB_TAKE_FRAME_LENGTH_CONTROL";
+
+ case TS_URB_RELEASE_FRAME_LENGTH_CONTROL:
+ return "TS_URB_RELEASE_FRAME_LENGTH_CONTROL";
+
+ case TS_URB_GET_FRAME_LENGTH:
+ return "TS_URB_GET_FRAME_LENGTH";
+
+ case TS_URB_SET_FRAME_LENGTH:
+ return "TS_URB_SET_FRAME_LENGTH";
+
+ case TS_URB_GET_CURRENT_FRAME_NUMBER:
+ return "TS_URB_GET_CURRENT_FRAME_NUMBER";
+
+ case TS_URB_CONTROL_TRANSFER:
+ return "TS_URB_CONTROL_TRANSFER";
+
+ case TS_URB_BULK_OR_INTERRUPT_TRANSFER:
+ return "TS_URB_BULK_OR_INTERRUPT_TRANSFER";
+
+ case TS_URB_ISOCH_TRANSFER:
+ return "TS_URB_ISOCH_TRANSFER";
+
+ case TS_URB_GET_DESCRIPTOR_FROM_DEVICE:
+ return "TS_URB_GET_DESCRIPTOR_FROM_DEVICE";
+
+ case TS_URB_SET_DESCRIPTOR_TO_DEVICE:
+ return "TS_URB_SET_DESCRIPTOR_TO_DEVICE";
+
+ case TS_URB_SET_FEATURE_TO_DEVICE:
+ return "TS_URB_SET_FEATURE_TO_DEVICE";
+
+ case TS_URB_SET_FEATURE_TO_INTERFACE:
+ return "TS_URB_SET_FEATURE_TO_INTERFACE";
+
+ case TS_URB_SET_FEATURE_TO_ENDPOINT:
+ return "TS_URB_SET_FEATURE_TO_ENDPOINT";
+
+ case TS_URB_CLEAR_FEATURE_TO_DEVICE:
+ return "TS_URB_CLEAR_FEATURE_TO_DEVICE";
+
+ case TS_URB_CLEAR_FEATURE_TO_INTERFACE:
+ return "TS_URB_CLEAR_FEATURE_TO_INTERFACE";
+
+ case TS_URB_CLEAR_FEATURE_TO_ENDPOINT:
+ return "TS_URB_CLEAR_FEATURE_TO_ENDPOINT";
+
+ case TS_URB_GET_STATUS_FROM_DEVICE:
+ return "TS_URB_GET_STATUS_FROM_DEVICE";
+
+ case TS_URB_GET_STATUS_FROM_INTERFACE:
+ return "TS_URB_GET_STATUS_FROM_INTERFACE";
+
+ case TS_URB_GET_STATUS_FROM_ENDPOINT:
+ return "TS_URB_GET_STATUS_FROM_ENDPOINT";
+
+ case TS_URB_RESERVED_0X0016:
+ return "TS_URB_RESERVED_0X0016";
+
+ case TS_URB_VENDOR_DEVICE:
+ return "TS_URB_VENDOR_DEVICE";
+
+ case TS_URB_VENDOR_INTERFACE:
+ return "TS_URB_VENDOR_INTERFACE";
+
+ case TS_URB_VENDOR_ENDPOINT:
+ return "TS_URB_VENDOR_ENDPOINT";
+
+ case TS_URB_CLASS_DEVICE:
+ return "TS_URB_CLASS_DEVICE";
+
+ case TS_URB_CLASS_INTERFACE:
+ return "TS_URB_CLASS_INTERFACE";
+
+ case TS_URB_CLASS_ENDPOINT:
+ return "TS_URB_CLASS_ENDPOINT";
+
+ case TS_URB_RESERVE_0X001D:
+ return "TS_URB_RESERVE_0X001D";
+
+ case TS_URB_SYNC_RESET_PIPE_AND_CLEAR_STALL:
+ return "TS_URB_SYNC_RESET_PIPE_AND_CLEAR_STALL";
+
+ case TS_URB_CLASS_OTHER:
+ return "TS_URB_CLASS_OTHER";
+
+ case TS_URB_VENDOR_OTHER:
+ return "TS_URB_VENDOR_OTHER";
+
+ case TS_URB_GET_STATUS_FROM_OTHER:
+ return "TS_URB_GET_STATUS_FROM_OTHER";
+
+ case TS_URB_CLEAR_FEATURE_TO_OTHER:
+ return "TS_URB_CLEAR_FEATURE_TO_OTHER";
+
+ case TS_URB_SET_FEATURE_TO_OTHER:
+ return "TS_URB_SET_FEATURE_TO_OTHER";
+
+ case TS_URB_GET_DESCRIPTOR_FROM_ENDPOINT:
+ return "TS_URB_GET_DESCRIPTOR_FROM_ENDPOINT";
+
+ case TS_URB_SET_DESCRIPTOR_TO_ENDPOINT:
+ return "TS_URB_SET_DESCRIPTOR_TO_ENDPOINT";
+
+ case TS_URB_CONTROL_GET_CONFIGURATION_REQUEST:
+ return "TS_URB_CONTROL_GET_CONFIGURATION_REQUEST";
+
+ case TS_URB_CONTROL_GET_INTERFACE_REQUEST:
+ return "TS_URB_CONTROL_GET_INTERFACE_REQUEST";
+
+ case TS_URB_GET_DESCRIPTOR_FROM_INTERFACE:
+ return "TS_URB_GET_DESCRIPTOR_FROM_INTERFACE";
+
+ case TS_URB_SET_DESCRIPTOR_TO_INTERFACE:
+ return "TS_URB_SET_DESCRIPTOR_TO_INTERFACE";
+
+ case TS_URB_GET_OS_FEATURE_DESCRIPTOR_REQUEST:
+ return "TS_URB_GET_OS_FEATURE_DESCRIPTOR_REQUEST";
+
+ case TS_URB_RESERVE_0X002B:
+ return "TS_URB_RESERVE_0X002B";
+
+ case TS_URB_RESERVE_0X002C:
+ return "TS_URB_RESERVE_0X002C";
+
+ case TS_URB_RESERVE_0X002D:
+ return "TS_URB_RESERVE_0X002D";
+
+ case TS_URB_RESERVE_0X002E:
+ return "TS_URB_RESERVE_0X002E";
+
+ case TS_URB_RESERVE_0X002F:
+ return "TS_URB_RESERVE_0X002F";
+
+ case TS_URB_SYNC_RESET_PIPE:
+ return "TS_URB_SYNC_RESET_PIPE";
+
+ case TS_URB_SYNC_CLEAR_STALL:
+ return "TS_URB_SYNC_CLEAR_STALL";
+
+ case TS_URB_CONTROL_TRANSFER_EX:
+ return "TS_URB_CONTROL_TRANSFER_EX";
+
+ default:
+ return "UNKNOWN";
+ }
+}
+
+void urbdrc_dump_message(wLog* log, BOOL client, BOOL write, wStream* s)
+{
+ const char* type = write ? "WRITE" : "READ";
+ UINT32 InterfaceId = 0;
+ UINT32 MessageId = 0;
+ UINT32 FunctionId = 0;
+ size_t length = 0;
+ size_t pos = 0;
+
+ pos = Stream_GetPosition(s);
+ if (write)
+ {
+ length = pos;
+ Stream_SetPosition(s, 0);
+ }
+ else
+ length = Stream_GetRemainingLength(s);
+
+ if (length < 12)
+ return;
+
+ Stream_Read_UINT32(s, InterfaceId);
+ Stream_Read_UINT32(s, MessageId);
+ Stream_Read_UINT32(s, FunctionId);
+ Stream_SetPosition(s, pos);
+
+ WLog_Print(log, WLOG_DEBUG,
+ "[%-5s] %s [%08" PRIx32 "] InterfaceId=%08" PRIx32 ", MessageId=%08" PRIx32
+ ", FunctionId=%08" PRIx32 ", length=%" PRIuz,
+ type, call_to_string(client, InterfaceId, FunctionId), FunctionId, InterfaceId,
+ MessageId, FunctionId, length);
+#if defined(WITH_DEBUG_URBDRC)
+ if (write)
+ WLog_Print(log, WLOG_TRACE, "-------------------------- URBDRC sent: ---");
+ else
+ WLog_Print(log, WLOG_TRACE, "-------------------------- URBDRC received:");
+ winpr_HexLogDump(log, WLOG_TRACE, Stream_Buffer(s), length);
+ WLog_Print(log, WLOG_TRACE, "-------------------------- URBDRC end -----");
+#endif
+}
diff --git a/channels/urbdrc/common/urbdrc_helpers.h b/channels/urbdrc/common/urbdrc_helpers.h
new file mode 100644
index 0000000..d766ac5
--- /dev/null
+++ b/channels/urbdrc/common/urbdrc_helpers.h
@@ -0,0 +1,45 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Server USB redirection channel - helper functions
+ *
+ * Copyright 2019 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2019 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_URBDRC_HELPERS_H
+#define FREERDP_CHANNEL_URBDRC_HELPERS_H
+
+#include <winpr/wtypes.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <winpr/wlog.h>
+#include <winpr/stream.h>
+
+ const char* urb_function_string(UINT16 urb);
+ const char* mask_to_string(UINT32 mask);
+ const char* interface_to_string(UINT32 id);
+ const char* call_to_string(BOOL client, UINT32 interfaceNr, UINT32 functionId);
+
+ void urbdrc_dump_message(wLog* log, BOOL client, BOOL write, wStream* s);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_URBDRC_HELPERS_H */
diff --git a/channels/urbdrc/common/urbdrc_types.h b/channels/urbdrc/common/urbdrc_types.h
new file mode 100644
index 0000000..a3deaf1
--- /dev/null
+++ b/channels/urbdrc/common/urbdrc_types.h
@@ -0,0 +1,306 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX USB Redirection
+ *
+ * Copyright 2012 Atrust corp.
+ * Copyright 2012 Alfred Liu <alfred.liu@atruscorp.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_URBDRC_CLIENT_TYPES_H
+#define FREERDP_CHANNEL_URBDRC_CLIENT_TYPES_H
+
+#include <freerdp/config.h>
+
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+
+#include <msusb.h>
+
+#include <winpr/stream.h>
+
+#define RIM_CAPABILITY_VERSION_01 0x00000001
+
+#define CAPABILITIES_NEGOTIATOR 0x00000000
+#define CLIENT_DEVICE_SINK 0x00000001
+#define SERVER_CHANNEL_NOTIFICATION 0x00000002
+#define CLIENT_CHANNEL_NOTIFICATION 0x00000003
+#define BASE_USBDEVICE_NUM 0x00000005
+
+#define RIMCALL_RELEASE 0x00000001
+#define RIMCALL_QUERYINTERFACE 0x00000002
+#define RIM_EXCHANGE_CAPABILITY_REQUEST 0x00000100
+#define CHANNEL_CREATED 0x00000100
+#define ADD_VIRTUAL_CHANNEL 0x00000100
+#define ADD_DEVICE 0x00000101
+
+#define INIT_CHANNEL_IN 1
+#define INIT_CHANNEL_OUT 0
+
+/* InterfaceClass */
+#define CLASS_RESERVE 0x00
+#define CLASS_AUDIO 0x01
+#define CLASS_COMMUNICATION_IF 0x02
+#define CLASS_HID 0x03
+#define CLASS_PHYSICAL 0x05
+#define CLASS_IMAGE 0x06
+#define CLASS_PRINTER 0x07
+#define CLASS_MASS_STORAGE 0x08
+#define CLASS_HUB 0x09
+#define CLASS_COMMUNICATION_DATA_IF 0x0a
+#define CLASS_SMART_CARD 0x0b
+#define CLASS_CONTENT_SECURITY 0x0d
+#define CLASS_VIDEO 0x0e
+#define CLASS_PERSONAL_HEALTHCARE 0x0f
+#define CLASS_DIAGNOSTIC 0xdc
+#define CLASS_WIRELESS_CONTROLLER 0xe0
+#define CLASS_ELSE_DEVICE 0xef
+#define CLASS_DEPENDENCE 0xfe
+#define CLASS_VENDOR_DEPENDENCE 0xff
+
+/* usb version */
+#define USB_v1_0 0x100
+#define USB_v1_1 0x110
+#define USB_v2_0 0x200
+#define USB_v3_0 0x300
+
+#define STREAM_ID_NONE 0x0UL
+#define STREAM_ID_PROXY 0x1UL
+#define STREAM_ID_STUB 0x2UL
+#define STREAM_ID_MASK 0xC0000000
+#define INTERFACE_ID_MASK 0x3FFFFFFF
+
+#define CANCEL_REQUEST 0x00000100
+#define REGISTER_REQUEST_CALLBACK 0x00000101
+#define IO_CONTROL 0x00000102
+#define INTERNAL_IO_CONTROL 0x00000103
+#define QUERY_DEVICE_TEXT 0x00000104
+
+#define TRANSFER_IN_REQUEST 0x00000105
+#define TRANSFER_OUT_REQUEST 0x00000106
+#define RETRACT_DEVICE 0x00000107
+
+#define IOCONTROL_COMPLETION 0x00000100
+#define URB_COMPLETION 0x00000101
+#define URB_COMPLETION_NO_DATA 0x00000102
+
+/* The USB device is to be stopped from being redirected because the
+ * device is blocked by the server's policy. */
+#define UsbRetractReason_BlockedByPolicy 0x00000001
+
+enum device_text_type
+{
+ DeviceTextDescription = 0,
+ DeviceTextLocationInformation = 1,
+};
+
+enum device_descriptor_table
+{
+ B_LENGTH = 0,
+ B_DESCRIPTOR_TYPE = 1,
+ BCD_USB = 2,
+ B_DEVICE_CLASS = 4,
+ B_DEVICE_SUBCLASS = 5,
+ B_DEVICE_PROTOCOL = 6,
+ B_MAX_PACKET_SIZE0 = 7,
+ ID_VENDOR = 8,
+ ID_PRODUCT = 10,
+ BCD_DEVICE = 12,
+ I_MANUFACTURER = 14,
+ I_PRODUCT = 15,
+ I_SERIAL_NUMBER = 16,
+ B_NUM_CONFIGURATIONS = 17
+};
+
+#define PIPE_CANCEL 0
+#define PIPE_RESET 1
+
+#define IOCTL_INTERNAL_USB_SUBMIT_URB 0x00220003
+#define IOCTL_INTERNAL_USB_RESET_PORT 0x00220007
+#define IOCTL_INTERNAL_USB_GET_PORT_STATUS 0x00220013
+#define IOCTL_INTERNAL_USB_CYCLE_PORT 0x0022001F
+#define IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION 0x00220027
+
+#define TS_URB_SELECT_CONFIGURATION 0x0000
+#define TS_URB_SELECT_INTERFACE 0x0001
+#define TS_URB_PIPE_REQUEST 0x0002
+#define TS_URB_TAKE_FRAME_LENGTH_CONTROL 0x0003
+#define TS_URB_RELEASE_FRAME_LENGTH_CONTROL 0x0004
+#define TS_URB_GET_FRAME_LENGTH 0x0005
+#define TS_URB_SET_FRAME_LENGTH 0x0006
+#define TS_URB_GET_CURRENT_FRAME_NUMBER 0x0007
+#define TS_URB_CONTROL_TRANSFER 0x0008
+#define TS_URB_BULK_OR_INTERRUPT_TRANSFER 0x0009
+#define TS_URB_ISOCH_TRANSFER 0x000A
+#define TS_URB_GET_DESCRIPTOR_FROM_DEVICE 0x000B
+#define TS_URB_SET_DESCRIPTOR_TO_DEVICE 0x000C
+#define TS_URB_SET_FEATURE_TO_DEVICE 0x000D
+#define TS_URB_SET_FEATURE_TO_INTERFACE 0x000E
+#define TS_URB_SET_FEATURE_TO_ENDPOINT 0x000F
+#define TS_URB_CLEAR_FEATURE_TO_DEVICE 0x0010
+#define TS_URB_CLEAR_FEATURE_TO_INTERFACE 0x0011
+#define TS_URB_CLEAR_FEATURE_TO_ENDPOINT 0x0012
+#define TS_URB_GET_STATUS_FROM_DEVICE 0x0013
+#define TS_URB_GET_STATUS_FROM_INTERFACE 0x0014
+#define TS_URB_GET_STATUS_FROM_ENDPOINT 0x0015
+#define TS_URB_RESERVED_0X0016 0x0016
+#define TS_URB_VENDOR_DEVICE 0x0017
+#define TS_URB_VENDOR_INTERFACE 0x0018
+#define TS_URB_VENDOR_ENDPOINT 0x0019
+#define TS_URB_CLASS_DEVICE 0x001A
+#define TS_URB_CLASS_INTERFACE 0x001B
+#define TS_URB_CLASS_ENDPOINT 0x001C
+#define TS_URB_RESERVE_0X001D 0x001D
+#define TS_URB_SYNC_RESET_PIPE_AND_CLEAR_STALL 0x001E
+#define TS_URB_CLASS_OTHER 0x001F
+#define TS_URB_VENDOR_OTHER 0x0020
+#define TS_URB_GET_STATUS_FROM_OTHER 0x0021
+#define TS_URB_CLEAR_FEATURE_TO_OTHER 0x0022
+#define TS_URB_SET_FEATURE_TO_OTHER 0x0023
+#define TS_URB_GET_DESCRIPTOR_FROM_ENDPOINT 0x0024
+#define TS_URB_SET_DESCRIPTOR_TO_ENDPOINT 0x0025
+#define TS_URB_CONTROL_GET_CONFIGURATION_REQUEST 0x0026
+#define TS_URB_CONTROL_GET_INTERFACE_REQUEST 0x0027
+#define TS_URB_GET_DESCRIPTOR_FROM_INTERFACE 0x0028
+#define TS_URB_SET_DESCRIPTOR_TO_INTERFACE 0x0029
+#define TS_URB_GET_OS_FEATURE_DESCRIPTOR_REQUEST 0x002A
+#define TS_URB_RESERVE_0X002B 0x002B
+#define TS_URB_RESERVE_0X002C 0x002C
+#define TS_URB_RESERVE_0X002D 0x002D
+#define TS_URB_RESERVE_0X002E 0x002E
+#define TS_URB_RESERVE_0X002F 0x002F
+// USB 2.0 calls start at 0x0030
+#define TS_URB_SYNC_RESET_PIPE 0x0030
+#define TS_URB_SYNC_CLEAR_STALL 0x0031
+#define TS_URB_CONTROL_TRANSFER_EX 0x0032
+
+#define USBD_STATUS_SUCCESS 0x0
+#define USBD_STATUS_PENDING 0x40000000
+#define USBD_STATUS_CANCELED 0xC0010000
+
+#define USBD_STATUS_INVALID_URB_FUNCTION 0x80000200
+#define USBD_STATUS_CRC 0xC0000001
+#define USBD_STATUS_BTSTUFF 0xC0000002
+#define USBD_STATUS_DATA_TOGGLE_MISMATCH 0xC0000003
+#define USBD_STATUS_STALL_PID 0xC0000004
+#define USBD_STATUS_DEV_NOT_RESPONDING 0xC0000005
+#define USBD_STATUS_PID_CHECK_FAILURE 0xC0000006
+#define USBD_STATUS_UNEXPECTED_PID 0xC0000007
+#define USBD_STATUS_DATA_OVERRUN 0xC0000008
+#define USBD_STATUS_DATA_UNDERRUN 0xC0000009
+#define USBD_STATUS_RESERVED1 0xC000000A
+#define USBD_STATUS_RESERVED2 0xC000000B
+#define USBD_STATUS_BUFFER_OVERRUN 0xC000000C
+#define USBD_STATUS_BUFFER_UNDERRUN 0xC000000D
+
+/* unknown */
+#define USBD_STATUS_NO_DATA 0xC000000E
+
+#define USBD_STATUS_NOT_ACCESSED 0xC000000F
+#define USBD_STATUS_FIFO 0xC0000010
+#define USBD_STATUS_XACT_ERROR 0xC0000011
+#define USBD_STATUS_BABBLE_DETECTED 0xC0000012
+#define USBD_STATUS_DATA_BUFFER_ERROR 0xC0000013
+
+#define USBD_STATUS_NOT_SUPPORTED 0xC0000E00
+#define USBD_STATUS_BUFFER_TOO_SMALL 0xC0003000
+#define USBD_STATUS_TIMEOUT 0xC0006000
+#define USBD_STATUS_DEVICE_GONE 0xC0007000
+
+#define USBD_STATUS_NO_MEMORY 0x80000100
+#define USBD_STATUS_INVALID_URB_FUNCTION 0x80000200
+#define USBD_STATUS_INVALID_PARAMETER 0x80000300
+#define USBD_STATUS_REQUEST_FAILED 0x80000500
+#define USBD_STATUS_INVALID_PIPE_HANDLE 0x80000600
+#define USBD_STATUS_ERROR_SHORT_TRANSFER 0x80000900
+
+// Values for URB TransferFlags Field
+//
+
+/*
+ Set if data moves device->host
+*/
+#define USBD_TRANSFER_DIRECTION 0x00000001
+/*
+ This bit if not set indicates that a short packet, and hence,
+ a short transfer is an error condition
+*/
+#define USBD_SHORT_TRANSFER_OK 0x00000002
+/*
+ Subit the iso transfer on the next frame
+*/
+#define USBD_START_ISO_TRANSFER_ASAP 0x00000004
+#define USBD_DEFAULT_PIPE_TRANSFER 0x00000008
+
+#define USBD_TRANSFER_DIRECTION_FLAG(flags) ((flags)&USBD_TRANSFER_DIRECTION)
+
+#define USBD_TRANSFER_DIRECTION_OUT 0
+#define USBD_TRANSFER_DIRECTION_IN 1
+
+#define VALID_TRANSFER_FLAGS_MASK USBD_SHORT_TRANSFER_OK | \
+ USBD_TRANSFER_DIRECTION | \
+ USBD_START_ISO_TRANSFER_ASAP | \
+ USBD_DEFAULT_PIPE_TRANSFER)
+
+#define ENDPOINT_HALT 0x00
+#define DEVICE_REMOTE_WAKEUP 0x01
+
+/* transfer type */
+#define CONTROL_TRANSFER 0x00
+#define ISOCHRONOUS_TRANSFER 0x01
+#define BULK_TRANSFER 0x02
+#define INTERRUPT_TRANSFER 0x03
+
+#define ClearHubFeature (0x2000 | LIBUSB_REQUEST_CLEAR_FEATURE)
+#define ClearPortFeature (0x2300 | LIBUSB_REQUEST_CLEAR_FEATURE)
+#define GetHubDescriptor (0xa000 | LIBUSB_REQUEST_GET_DESCRIPTOR)
+#define GetHubStatus (0xa000 | LIBUSB_REQUEST_GET_STATUS)
+#define GetPortStatus (0xa300 | LIBUSB_REQUEST_GET_STATUS)
+#define SetHubFeature (0x2000 | LIBUSB_REQUEST_SET_FEATURE)
+#define SetPortFeature (0x2300 | LIBUSB_REQUEST_SET_FEATURE)
+
+#define USBD_PF_CHANGE_MAX_PACKET 0x00000001
+#define USBD_PF_SHORT_PACKET_OPT 0x00000002
+#define USBD_PF_ENABLE_RT_THREAD_ACCESS 0x00000004
+#define USBD_PF_MAP_ADD_TRANSFERS 0x00000008
+
+/* feature request */
+#define URB_SET_FEATURE 0x00
+#define URB_CLEAR_FEATURE 0x01
+
+#define USBD_PF_CHANGE_MAX_PACKET 0x00000001
+#define USBD_PF_SHORT_PACKET_OPT 0x00000002
+#define USBD_PF_ENABLE_RT_THREAD_ACCESS 0x00000004
+#define USBD_PF_MAP_ADD_TRANSFERS 0x00000008
+
+#define URB_CONTROL_TRANSFER_EXTERNAL 0x1
+#define URB_CONTROL_TRANSFER_NONEXTERNAL 0x0
+
+#define USBFS_URB_SHORT_NOT_OK 0x01
+#define USBFS_URB_ISO_ASAP 0x02
+#define USBFS_URB_BULK_CONTINUATION 0x04
+#define USBFS_URB_QUEUE_BULK 0x10
+
+#define URBDRC_DEVICE_INITIALIZED 0x01
+#define URBDRC_DEVICE_NOT_FOUND 0x02
+#define URBDRC_DEVICE_CHANNEL_CLOSED 0x08
+#define URBDRC_DEVICE_ALREADY_SEND 0x10
+#define URBDRC_DEVICE_DETACH_KERNEL 0x20
+
+#define UDEVMAN_FLAG_ADD_BY_VID_PID 0x01
+#define UDEVMAN_FLAG_ADD_BY_ADDR 0x02
+#define UDEVMAN_FLAG_ADD_BY_AUTO 0x04
+#define UDEVMAN_FLAG_DEBUG 0x08
+
+#endif /* FREERDP_CHANNEL_URBDRC_CLIENT_TYPES_H */
diff --git a/channels/video/CMakeLists.txt b/channels/video/CMakeLists.txt
new file mode 100644
index 0000000..f03c851
--- /dev/null
+++ b/channels/video/CMakeLists.txt
@@ -0,0 +1,22 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2017 David Fort <contact@hardening-consulting.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel("video")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/video/ChannelOptions.cmake b/channels/video/ChannelOptions.cmake
new file mode 100644
index 0000000..e7f9ce8
--- /dev/null
+++ b/channels/video/ChannelOptions.cmake
@@ -0,0 +1,12 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT OFF)
+
+define_channel_options(NAME "video" TYPE "dynamic"
+ DESCRIPTION "Video optimized remoting Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPEVOR]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+
diff --git a/channels/video/client/CMakeLists.txt b/channels/video/client/CMakeLists.txt
new file mode 100644
index 0000000..68d3e31
--- /dev/null
+++ b/channels/video/client/CMakeLists.txt
@@ -0,0 +1,30 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2018 David Fort <contact@hardening-consulting.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client("video")
+
+set(${MODULE_PREFIX}_SRCS
+ video_main.c
+ video_main.h
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+)
+include_directories(..)
+
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
diff --git a/channels/video/client/video_main.c b/channels/video/client/video_main.c
new file mode 100644
index 0000000..a23f54c
--- /dev/null
+++ b/channels/video/client/video_main.c
@@ -0,0 +1,1229 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Optimized Remoting Virtual Channel Extension
+ *
+ * Copyright 2017 David Fort <contact@hardening-consulting.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/synch.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+#include <winpr/cmdline.h>
+#include <winpr/collections.h>
+#include <winpr/interlocked.h>
+#include <winpr/sysinfo.h>
+
+#include <freerdp/addin.h>
+#include <freerdp/primitives.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/client/geometry.h>
+#include <freerdp/client/video.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/codec/h264.h>
+#include <freerdp/codec/yuv.h>
+
+#define TAG CHANNELS_TAG("video")
+
+#include "video_main.h"
+
+typedef struct
+{
+ IWTSPlugin wtsPlugin;
+
+ IWTSListener* controlListener;
+ IWTSListener* dataListener;
+ GENERIC_LISTENER_CALLBACK* control_callback;
+ GENERIC_LISTENER_CALLBACK* data_callback;
+
+ VideoClientContext* context;
+ BOOL initialized;
+} VIDEO_PLUGIN;
+
+#define XF_VIDEO_UNLIMITED_RATE 31
+
+static const BYTE MFVideoFormat_H264[] = { 'H', '2', '6', '4', 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 };
+
+typedef struct
+{
+ VideoClientContext* video;
+ BYTE PresentationId;
+ UINT32 ScaledWidth, ScaledHeight;
+ MAPPED_GEOMETRY* geometry;
+
+ UINT64 startTimeStamp;
+ UINT64 publishOffset;
+ H264_CONTEXT* h264;
+ wStream* currentSample;
+ UINT64 lastPublishTime, nextPublishTime;
+ volatile LONG refCounter;
+ VideoSurface* surface;
+} PresentationContext;
+
+typedef struct
+{
+ UINT64 publishTime;
+ UINT64 hnsDuration;
+ MAPPED_GEOMETRY* geometry;
+ UINT32 w, h;
+ UINT32 scanline;
+ BYTE* surfaceData;
+ PresentationContext* presentation;
+} VideoFrame;
+
+/** @brief private data for the channel */
+struct s_VideoClientContextPriv
+{
+ VideoClientContext* video;
+ GeometryClientContext* geometry;
+ wQueue* frames;
+ CRITICAL_SECTION framesLock;
+ wBufferPool* surfacePool;
+ UINT32 publishedFrames;
+ UINT32 droppedFrames;
+ UINT32 lastSentRate;
+ UINT64 nextFeedbackTime;
+ PresentationContext* currentPresentation;
+};
+
+static void PresentationContext_unref(PresentationContext** presentation);
+static void VideoClientContextPriv_free(VideoClientContextPriv* priv);
+
+static const char* video_command_name(BYTE cmd)
+{
+ switch (cmd)
+ {
+ case TSMM_START_PRESENTATION:
+ return "start";
+ case TSMM_STOP_PRESENTATION:
+ return "stop";
+ default:
+ return "<unknown>";
+ }
+}
+
+static void video_client_context_set_geometry(VideoClientContext* video,
+ GeometryClientContext* geometry)
+{
+ WINPR_ASSERT(video);
+ WINPR_ASSERT(video->priv);
+ video->priv->geometry = geometry;
+}
+
+static VideoClientContextPriv* VideoClientContextPriv_new(VideoClientContext* video)
+{
+ VideoClientContextPriv* ret = NULL;
+
+ WINPR_ASSERT(video);
+ ret = calloc(1, sizeof(*ret));
+ if (!ret)
+ return NULL;
+
+ ret->frames = Queue_New(TRUE, 10, 2);
+ if (!ret->frames)
+ {
+ WLog_ERR(TAG, "unable to allocate frames queue");
+ goto fail;
+ }
+
+ ret->surfacePool = BufferPool_New(FALSE, 0, 16);
+ if (!ret->surfacePool)
+ {
+ WLog_ERR(TAG, "unable to create surface pool");
+ goto fail;
+ }
+
+ if (!InitializeCriticalSectionAndSpinCount(&ret->framesLock, 4 * 1000))
+ {
+ WLog_ERR(TAG, "unable to initialize frames lock");
+ goto fail;
+ }
+
+ ret->video = video;
+
+ /* don't set to unlimited so that we have the chance to send a feedback in
+ * the first second (for servers that want feedback directly)
+ */
+ ret->lastSentRate = 30;
+ return ret;
+
+fail:
+ VideoClientContextPriv_free(ret);
+ return NULL;
+}
+
+static BOOL PresentationContext_ref(PresentationContext* presentation)
+{
+ WINPR_ASSERT(presentation);
+
+ InterlockedIncrement(&presentation->refCounter);
+ return TRUE;
+}
+
+static PresentationContext* PresentationContext_new(VideoClientContext* video, BYTE PresentationId,
+ UINT32 x, UINT32 y, UINT32 width, UINT32 height)
+{
+ size_t s = width * height * 4ULL;
+ VideoClientContextPriv* priv = NULL;
+ PresentationContext* ret = NULL;
+
+ WINPR_ASSERT(video);
+
+ priv = video->priv;
+ WINPR_ASSERT(priv);
+
+ if (s > INT32_MAX)
+ return NULL;
+
+ ret = calloc(1, sizeof(*ret));
+ if (!ret)
+ return NULL;
+
+ ret->video = video;
+ ret->PresentationId = PresentationId;
+
+ ret->h264 = h264_context_new(FALSE);
+ if (!ret->h264)
+ {
+ WLog_ERR(TAG, "unable to create a h264 context");
+ goto fail;
+ }
+ if (!h264_context_reset(ret->h264, width, height))
+ goto fail;
+
+ ret->currentSample = Stream_New(NULL, 4096);
+ if (!ret->currentSample)
+ {
+ WLog_ERR(TAG, "unable to create current packet stream");
+ goto fail;
+ }
+
+ ret->surface = video->createSurface(video, x, y, width, height);
+ if (!ret->surface)
+ {
+ WLog_ERR(TAG, "unable to create surface");
+ goto fail;
+ }
+
+ if (!PresentationContext_ref(ret))
+ goto fail;
+
+ return ret;
+
+fail:
+ PresentationContext_unref(&ret);
+ return NULL;
+}
+
+static void PresentationContext_unref(PresentationContext** ppresentation)
+{
+ PresentationContext* presentation = NULL;
+ MAPPED_GEOMETRY* geometry = NULL;
+
+ WINPR_ASSERT(ppresentation);
+
+ presentation = *ppresentation;
+ if (!presentation)
+ return;
+
+ if (InterlockedDecrement(&presentation->refCounter) > 0)
+ return;
+
+ geometry = presentation->geometry;
+ if (geometry)
+ {
+ geometry->MappedGeometryUpdate = NULL;
+ geometry->MappedGeometryClear = NULL;
+ geometry->custom = NULL;
+ mappedGeometryUnref(geometry);
+ }
+
+ h264_context_free(presentation->h264);
+ Stream_Free(presentation->currentSample, TRUE);
+ presentation->video->deleteSurface(presentation->video, presentation->surface);
+ free(presentation);
+ *ppresentation = NULL;
+}
+
+static void VideoFrame_free(VideoFrame** pframe)
+{
+ VideoFrame* frame = NULL;
+
+ WINPR_ASSERT(pframe);
+ frame = *pframe;
+ if (!frame)
+ return;
+
+ mappedGeometryUnref(frame->geometry);
+
+ WINPR_ASSERT(frame->presentation);
+ WINPR_ASSERT(frame->presentation->video);
+ WINPR_ASSERT(frame->presentation->video->priv);
+ BufferPool_Return(frame->presentation->video->priv->surfacePool, frame->surfaceData);
+ PresentationContext_unref(&frame->presentation);
+ free(frame);
+ *pframe = NULL;
+}
+
+static VideoFrame* VideoFrame_new(VideoClientContextPriv* priv, PresentationContext* presentation,
+ MAPPED_GEOMETRY* geom)
+{
+ VideoFrame* frame = NULL;
+ const VideoSurface* surface = NULL;
+
+ WINPR_ASSERT(priv);
+ WINPR_ASSERT(presentation);
+ WINPR_ASSERT(geom);
+
+ surface = presentation->surface;
+ WINPR_ASSERT(surface);
+
+ frame = calloc(1, sizeof(VideoFrame));
+ if (!frame)
+ goto fail;
+
+ mappedGeometryRef(geom);
+
+ frame->publishTime = presentation->lastPublishTime;
+ frame->geometry = geom;
+ frame->w = surface->alignedWidth;
+ frame->h = surface->alignedHeight;
+ frame->scanline = surface->scanline;
+
+ frame->surfaceData = BufferPool_Take(priv->surfacePool, 1ull * frame->scanline * frame->h);
+ if (!frame->surfaceData)
+ goto fail;
+
+ frame->presentation = presentation;
+ if (!PresentationContext_ref(frame->presentation))
+ goto fail;
+
+ return frame;
+
+fail:
+ VideoFrame_free(&frame);
+ return NULL;
+}
+
+void VideoClientContextPriv_free(VideoClientContextPriv* priv)
+{
+ if (!priv)
+ return;
+
+ EnterCriticalSection(&priv->framesLock);
+
+ if (priv->frames)
+ {
+ while (Queue_Count(priv->frames))
+ {
+ VideoFrame* frame = Queue_Dequeue(priv->frames);
+ if (frame)
+ VideoFrame_free(&frame);
+ }
+ }
+
+ Queue_Free(priv->frames);
+ LeaveCriticalSection(&priv->framesLock);
+
+ DeleteCriticalSection(&priv->framesLock);
+
+ if (priv->currentPresentation)
+ PresentationContext_unref(&priv->currentPresentation);
+
+ BufferPool_Free(priv->surfacePool);
+ free(priv);
+}
+
+static UINT video_control_send_presentation_response(VideoClientContext* context,
+ TSMM_PRESENTATION_RESPONSE* resp)
+{
+ BYTE buf[12] = { 0 };
+ wStream* s = NULL;
+ VIDEO_PLUGIN* video = NULL;
+ IWTSVirtualChannel* channel = NULL;
+ UINT ret = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(resp);
+
+ video = (VIDEO_PLUGIN*)context->handle;
+ WINPR_ASSERT(video);
+
+ s = Stream_New(buf, 12);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT32(s, 12); /* cbSize */
+ Stream_Write_UINT32(s, TSMM_PACKET_TYPE_PRESENTATION_RESPONSE); /* PacketType */
+ Stream_Write_UINT8(s, resp->PresentationId);
+ Stream_Zero(s, 3);
+ Stream_SealLength(s);
+
+ channel = video->control_callback->channel_callback->channel;
+ ret = channel->Write(channel, 12, buf, NULL);
+ Stream_Free(s, FALSE);
+
+ return ret;
+}
+
+static BOOL video_onMappedGeometryUpdate(MAPPED_GEOMETRY* geometry)
+{
+ PresentationContext* presentation = NULL;
+ RDP_RECT* r = NULL;
+
+ WINPR_ASSERT(geometry);
+
+ presentation = (PresentationContext*)geometry->custom;
+ WINPR_ASSERT(presentation);
+
+ r = &geometry->geometry.boundingRect;
+ WLog_DBG(TAG,
+ "geometry updated topGeom=(%" PRId32 ",%" PRId32 "-%" PRId32 "x%" PRId32
+ ") geom=(%" PRId32 ",%" PRId32 "-%" PRId32 "x%" PRId32 ") rects=(%" PRId16 ",%" PRId16
+ "-%" PRId16 "x%" PRId16 ")",
+ geometry->topLevelLeft, geometry->topLevelTop,
+ geometry->topLevelRight - geometry->topLevelLeft,
+ geometry->topLevelBottom - geometry->topLevelTop,
+
+ geometry->left, geometry->top, geometry->right - geometry->left,
+ geometry->bottom - geometry->top,
+
+ r->x, r->y, r->width, r->height);
+
+ presentation->surface->x = geometry->topLevelLeft + geometry->left;
+ presentation->surface->y = geometry->topLevelTop + geometry->top;
+
+ return TRUE;
+}
+
+static BOOL video_onMappedGeometryClear(MAPPED_GEOMETRY* geometry)
+{
+ PresentationContext* presentation = NULL;
+
+ WINPR_ASSERT(geometry);
+
+ presentation = (PresentationContext*)geometry->custom;
+ WINPR_ASSERT(presentation);
+
+ mappedGeometryUnref(presentation->geometry);
+ presentation->geometry = NULL;
+ return TRUE;
+}
+
+static UINT video_PresentationRequest(VideoClientContext* video,
+ const TSMM_PRESENTATION_REQUEST* req)
+{
+ UINT ret = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(video);
+ WINPR_ASSERT(req);
+
+ VideoClientContextPriv* priv = video->priv;
+ WINPR_ASSERT(priv);
+
+ if (req->Command == TSMM_START_PRESENTATION)
+ {
+ MAPPED_GEOMETRY* geom = NULL;
+ TSMM_PRESENTATION_RESPONSE resp;
+
+ if (memcmp(req->VideoSubtypeId, MFVideoFormat_H264, 16) != 0)
+ {
+ WLog_ERR(TAG, "not a H264 video, ignoring request");
+ return CHANNEL_RC_OK;
+ }
+
+ if (priv->currentPresentation)
+ {
+ if (priv->currentPresentation->PresentationId == req->PresentationId)
+ {
+ WLog_ERR(TAG, "ignoring start request for existing presentation %" PRIu8,
+ req->PresentationId);
+ return CHANNEL_RC_OK;
+ }
+
+ WLog_ERR(TAG, "releasing current presentation %" PRIu8, req->PresentationId);
+ PresentationContext_unref(&priv->currentPresentation);
+ }
+
+ if (!priv->geometry)
+ {
+ WLog_ERR(TAG, "geometry channel not ready, ignoring request");
+ return CHANNEL_RC_OK;
+ }
+
+ geom = HashTable_GetItemValue(priv->geometry->geometries, &(req->GeometryMappingId));
+ if (!geom)
+ {
+ WLog_ERR(TAG, "geometry mapping 0x%" PRIx64 " not registered", req->GeometryMappingId);
+ return CHANNEL_RC_OK;
+ }
+
+ WLog_DBG(TAG, "creating presentation 0x%x", req->PresentationId);
+ priv->currentPresentation = PresentationContext_new(
+ video, req->PresentationId, geom->topLevelLeft + geom->left,
+ geom->topLevelTop + geom->top, req->SourceWidth, req->SourceHeight);
+ if (!priv->currentPresentation)
+ {
+ WLog_ERR(TAG, "unable to create presentation video");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ mappedGeometryRef(geom);
+ priv->currentPresentation->geometry = geom;
+
+ priv->currentPresentation->video = video;
+ priv->currentPresentation->ScaledWidth = req->ScaledWidth;
+ priv->currentPresentation->ScaledHeight = req->ScaledHeight;
+
+ geom->custom = priv->currentPresentation;
+ geom->MappedGeometryUpdate = video_onMappedGeometryUpdate;
+ geom->MappedGeometryClear = video_onMappedGeometryClear;
+
+ /* send back response */
+ resp.PresentationId = req->PresentationId;
+ ret = video_control_send_presentation_response(video, &resp);
+ }
+ else if (req->Command == TSMM_STOP_PRESENTATION)
+ {
+ WLog_DBG(TAG, "stopping presentation 0x%x", req->PresentationId);
+ if (!priv->currentPresentation)
+ {
+ WLog_ERR(TAG, "unknown presentation to stop %" PRIu8, req->PresentationId);
+ return CHANNEL_RC_OK;
+ }
+
+ priv->droppedFrames = 0;
+ priv->publishedFrames = 0;
+ PresentationContext_unref(&priv->currentPresentation);
+ }
+
+ return ret;
+}
+
+static UINT video_read_tsmm_presentation_req(VideoClientContext* context, wStream* s)
+{
+ TSMM_PRESENTATION_REQUEST req = { 0 };
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 60))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT8(s, req.PresentationId);
+ Stream_Read_UINT8(s, req.Version);
+ Stream_Read_UINT8(s, req.Command);
+ Stream_Read_UINT8(s, req.FrameRate); /* FrameRate - reserved and ignored */
+
+ Stream_Seek_UINT16(s); /* AverageBitrateKbps reserved and ignored */
+ Stream_Seek_UINT16(s); /* reserved */
+
+ Stream_Read_UINT32(s, req.SourceWidth);
+ Stream_Read_UINT32(s, req.SourceHeight);
+ Stream_Read_UINT32(s, req.ScaledWidth);
+ Stream_Read_UINT32(s, req.ScaledHeight);
+ Stream_Read_UINT64(s, req.hnsTimestampOffset);
+ Stream_Read_UINT64(s, req.GeometryMappingId);
+ Stream_Read(s, req.VideoSubtypeId, 16);
+
+ Stream_Read_UINT32(s, req.cbExtra);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, req.cbExtra))
+ return ERROR_INVALID_DATA;
+
+ req.pExtraData = Stream_Pointer(s);
+
+ WLog_DBG(TAG,
+ "presentationReq: id:%" PRIu8 " version:%" PRIu8
+ " command:%s srcWidth/srcHeight=%" PRIu32 "x%" PRIu32 " scaled Width/Height=%" PRIu32
+ "x%" PRIu32 " timestamp=%" PRIu64 " mappingId=%" PRIx64 "",
+ req.PresentationId, req.Version, video_command_name(req.Command), req.SourceWidth,
+ req.SourceHeight, req.ScaledWidth, req.ScaledHeight, req.hnsTimestampOffset,
+ req.GeometryMappingId);
+
+ return video_PresentationRequest(context, &req);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT video_control_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* s)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+ VIDEO_PLUGIN* video = NULL;
+ VideoClientContext* context = NULL;
+ UINT ret = CHANNEL_RC_OK;
+ UINT32 cbSize = 0;
+ UINT32 packetType = 0;
+
+ WINPR_ASSERT(callback);
+ WINPR_ASSERT(s);
+
+ video = (VIDEO_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(video);
+
+ context = (VideoClientContext*)video->wtsPlugin.pInterface;
+ WINPR_ASSERT(context);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, cbSize);
+ if (cbSize < 8)
+ {
+ WLog_ERR(TAG, "invalid cbSize %" PRIu32 ", expected 8", cbSize);
+ return ERROR_INVALID_DATA;
+ }
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, cbSize - 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, packetType);
+ switch (packetType)
+ {
+ case TSMM_PACKET_TYPE_PRESENTATION_REQUEST:
+ ret = video_read_tsmm_presentation_req(context, s);
+ break;
+ default:
+ WLog_ERR(TAG, "not expecting packet type %" PRIu32 "", packetType);
+ ret = ERROR_UNSUPPORTED_TYPE;
+ break;
+ }
+
+ return ret;
+}
+
+static UINT video_control_send_client_notification(VideoClientContext* context,
+ const TSMM_CLIENT_NOTIFICATION* notif)
+{
+ BYTE buf[100];
+ wStream* s = NULL;
+ VIDEO_PLUGIN* video = NULL;
+ IWTSVirtualChannel* channel = NULL;
+ UINT ret = 0;
+ UINT32 cbSize = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(notif);
+
+ video = (VIDEO_PLUGIN*)context->handle;
+ WINPR_ASSERT(video);
+
+ s = Stream_New(buf, 32);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ cbSize = 16;
+ Stream_Seek_UINT32(s); /* cbSize */
+ Stream_Write_UINT32(s, TSMM_PACKET_TYPE_CLIENT_NOTIFICATION); /* PacketType */
+ Stream_Write_UINT8(s, notif->PresentationId);
+ Stream_Write_UINT8(s, notif->NotificationType);
+ Stream_Zero(s, 2);
+ if (notif->NotificationType == TSMM_CLIENT_NOTIFICATION_TYPE_FRAMERATE_OVERRIDE)
+ {
+ Stream_Write_UINT32(s, 16); /* cbData */
+
+ /* TSMM_CLIENT_NOTIFICATION_FRAMERATE_OVERRIDE */
+ Stream_Write_UINT32(s, notif->FramerateOverride.Flags);
+ Stream_Write_UINT32(s, notif->FramerateOverride.DesiredFrameRate);
+ Stream_Zero(s, 4 * 2);
+
+ cbSize += 4 * 4;
+ }
+ else
+ {
+ Stream_Write_UINT32(s, 0); /* cbData */
+ }
+
+ Stream_SealLength(s);
+ Stream_SetPosition(s, 0);
+ Stream_Write_UINT32(s, cbSize);
+ Stream_Free(s, FALSE);
+
+ WINPR_ASSERT(video->control_callback);
+ WINPR_ASSERT(video->control_callback->channel_callback);
+
+ channel = video->control_callback->channel_callback->channel;
+ WINPR_ASSERT(channel);
+ WINPR_ASSERT(channel->Write);
+
+ ret = channel->Write(channel, cbSize, buf, NULL);
+
+ return ret;
+}
+
+static void video_timer(VideoClientContext* video, UINT64 now)
+{
+ PresentationContext* presentation = NULL;
+ VideoClientContextPriv* priv = NULL;
+ VideoFrame* peekFrame = NULL;
+ VideoFrame* frame = NULL;
+
+ WINPR_ASSERT(video);
+
+ priv = video->priv;
+ WINPR_ASSERT(priv);
+
+ EnterCriticalSection(&priv->framesLock);
+ do
+ {
+ peekFrame = (VideoFrame*)Queue_Peek(priv->frames);
+ if (!peekFrame)
+ break;
+
+ if (peekFrame->publishTime > now)
+ break;
+
+ if (frame)
+ {
+ WLog_DBG(TAG, "dropping frame @%" PRIu64, frame->publishTime);
+ priv->droppedFrames++;
+ VideoFrame_free(&frame);
+ }
+ frame = peekFrame;
+ Queue_Dequeue(priv->frames);
+ } while (1);
+ LeaveCriticalSection(&priv->framesLock);
+
+ if (!frame)
+ goto treat_feedback;
+
+ presentation = frame->presentation;
+
+ priv->publishedFrames++;
+ memcpy(presentation->surface->data, frame->surfaceData, 1ull * frame->scanline * frame->h);
+
+ WINPR_ASSERT(video->showSurface);
+ video->showSurface(video, presentation->surface, presentation->ScaledWidth,
+ presentation->ScaledHeight);
+
+ VideoFrame_free(&frame);
+
+treat_feedback:
+ if (priv->nextFeedbackTime < now)
+ {
+ /* we can compute some feedback only if we have some published frames and
+ * a current presentation
+ */
+ if (priv->publishedFrames && priv->currentPresentation)
+ {
+ UINT32 computedRate = 0;
+
+ PresentationContext_ref(priv->currentPresentation);
+
+ if (priv->droppedFrames)
+ {
+ /**
+ * some dropped frames, looks like we're asking too many frames per seconds,
+ * try lowering rate. We go directly from unlimited rate to 24 frames/seconds
+ * otherwise we lower rate by 2 frames by seconds
+ */
+ if (priv->lastSentRate == XF_VIDEO_UNLIMITED_RATE)
+ computedRate = 24;
+ else
+ {
+ computedRate = priv->lastSentRate - 2;
+ if (!computedRate)
+ computedRate = 2;
+ }
+ }
+ else
+ {
+ /**
+ * we treat all frames ok, so either ask the server to send more,
+ * or stay unlimited
+ */
+ if (priv->lastSentRate == XF_VIDEO_UNLIMITED_RATE)
+ computedRate = XF_VIDEO_UNLIMITED_RATE; /* stay unlimited */
+ else
+ {
+ computedRate = priv->lastSentRate + 2;
+ if (computedRate > XF_VIDEO_UNLIMITED_RATE)
+ computedRate = XF_VIDEO_UNLIMITED_RATE;
+ }
+ }
+
+ if (computedRate != priv->lastSentRate)
+ {
+ TSMM_CLIENT_NOTIFICATION notif;
+
+ WINPR_ASSERT(priv->currentPresentation);
+ notif.PresentationId = priv->currentPresentation->PresentationId;
+ notif.NotificationType = TSMM_CLIENT_NOTIFICATION_TYPE_FRAMERATE_OVERRIDE;
+ if (computedRate == XF_VIDEO_UNLIMITED_RATE)
+ {
+ notif.FramerateOverride.Flags = 0x01;
+ notif.FramerateOverride.DesiredFrameRate = 0x00;
+ }
+ else
+ {
+ notif.FramerateOverride.Flags = 0x02;
+ notif.FramerateOverride.DesiredFrameRate = computedRate;
+ }
+
+ video_control_send_client_notification(video, &notif);
+ priv->lastSentRate = computedRate;
+
+ WLog_DBG(TAG,
+ "server notified with rate %" PRIu32 " published=%" PRIu32
+ " dropped=%" PRIu32,
+ priv->lastSentRate, priv->publishedFrames, priv->droppedFrames);
+ }
+
+ PresentationContext_unref(&priv->currentPresentation);
+ }
+
+ WLog_DBG(TAG, "currentRate=%" PRIu32 " published=%" PRIu32 " dropped=%" PRIu32,
+ priv->lastSentRate, priv->publishedFrames, priv->droppedFrames);
+
+ priv->droppedFrames = 0;
+ priv->publishedFrames = 0;
+ priv->nextFeedbackTime = now + 1000;
+ }
+}
+
+static UINT video_VideoData(VideoClientContext* context, const TSMM_VIDEO_DATA* data)
+{
+ VideoClientContextPriv* priv = NULL;
+ PresentationContext* presentation = NULL;
+ int status = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(data);
+
+ priv = context->priv;
+ WINPR_ASSERT(priv);
+
+ presentation = priv->currentPresentation;
+ if (!presentation)
+ {
+ WLog_ERR(TAG, "no current presentation");
+ return CHANNEL_RC_OK;
+ }
+
+ if (presentation->PresentationId != data->PresentationId)
+ {
+ WLog_ERR(TAG, "current presentation id=%" PRIu8 " doesn't match data id=%" PRIu8,
+ presentation->PresentationId, data->PresentationId);
+ return CHANNEL_RC_OK;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(presentation->currentSample, data->cbSample))
+ {
+ WLog_ERR(TAG, "unable to expand the current packet");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write(presentation->currentSample, data->pSample, data->cbSample);
+
+ if (data->CurrentPacketIndex == data->PacketsInSample)
+ {
+ VideoSurface* surface = presentation->surface;
+ H264_CONTEXT* h264 = presentation->h264;
+ UINT64 startTime = GetTickCount64();
+ UINT64 timeAfterH264 = 0;
+ MAPPED_GEOMETRY* geom = presentation->geometry;
+
+ const RECTANGLE_16 rect = { 0, 0, surface->alignedWidth, surface->alignedHeight };
+ Stream_SealLength(presentation->currentSample);
+ Stream_SetPosition(presentation->currentSample, 0);
+
+ timeAfterH264 = GetTickCount64();
+ if (data->SampleNumber == 1)
+ {
+ presentation->lastPublishTime = startTime;
+ }
+
+ presentation->lastPublishTime += (data->hnsDuration / 10000);
+ if (presentation->lastPublishTime <= timeAfterH264 + 10)
+ {
+ int dropped = 0;
+
+ /* if the frame is to be published in less than 10 ms, let's consider it's now */
+ status = avc420_decompress(h264, Stream_Pointer(presentation->currentSample),
+ Stream_Length(presentation->currentSample), surface->data,
+ surface->format, surface->scanline, surface->alignedWidth,
+ surface->alignedHeight, &rect, 1);
+
+ if (status < 0)
+ return CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context->showSurface);
+ context->showSurface(context, presentation->surface, presentation->ScaledWidth,
+ presentation->ScaledHeight);
+
+ priv->publishedFrames++;
+
+ /* cleanup previously scheduled frames */
+ EnterCriticalSection(&priv->framesLock);
+ while (Queue_Count(priv->frames) > 0)
+ {
+ VideoFrame* frame = Queue_Dequeue(priv->frames);
+ if (frame)
+ {
+ priv->droppedFrames++;
+ VideoFrame_free(&frame);
+ dropped++;
+ }
+ }
+ LeaveCriticalSection(&priv->framesLock);
+
+ if (dropped)
+ WLog_DBG(TAG, "showing frame (%d dropped)", dropped);
+ }
+ else
+ {
+ BOOL enqueueResult = 0;
+ VideoFrame* frame = VideoFrame_new(priv, presentation, geom);
+ if (!frame)
+ {
+ WLog_ERR(TAG, "unable to create frame");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ status = avc420_decompress(h264, Stream_Pointer(presentation->currentSample),
+ Stream_Length(presentation->currentSample),
+ frame->surfaceData, surface->format, surface->scanline,
+ surface->alignedWidth, surface->alignedHeight, &rect, 1);
+ if (status < 0)
+ {
+ VideoFrame_free(&frame);
+ return CHANNEL_RC_OK;
+ }
+
+ EnterCriticalSection(&priv->framesLock);
+ enqueueResult = Queue_Enqueue(priv->frames, frame);
+ LeaveCriticalSection(&priv->framesLock);
+
+ if (!enqueueResult)
+ {
+ WLog_ERR(TAG, "unable to enqueue frame");
+ VideoFrame_free(&frame);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): Queue_Enqueue owns frame
+ WLog_DBG(TAG, "scheduling frame in %" PRIu32 " ms", (frame->publishTime - startTime));
+ }
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT video_data_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* s)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+ VIDEO_PLUGIN* video = NULL;
+ VideoClientContext* context = NULL;
+ UINT32 cbSize = 0;
+ UINT32 packetType = 0;
+ TSMM_VIDEO_DATA data;
+
+ video = (VIDEO_PLUGIN*)callback->plugin;
+ context = (VideoClientContext*)video->wtsPlugin.pInterface;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, cbSize);
+ if (cbSize < 8)
+ {
+ WLog_ERR(TAG, "invalid cbSize %" PRIu32 ", expected >= 8", cbSize);
+ return ERROR_INVALID_DATA;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, cbSize - 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, packetType);
+ if (packetType != TSMM_PACKET_TYPE_VIDEO_DATA)
+ {
+ WLog_ERR(TAG, "only expecting VIDEO_DATA on the data channel");
+ return ERROR_INVALID_DATA;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT8(s, data.PresentationId);
+ Stream_Read_UINT8(s, data.Version);
+ Stream_Read_UINT8(s, data.Flags);
+ Stream_Seek_UINT8(s); /* reserved */
+ Stream_Read_UINT64(s, data.hnsTimestamp);
+ Stream_Read_UINT64(s, data.hnsDuration);
+ Stream_Read_UINT16(s, data.CurrentPacketIndex);
+ Stream_Read_UINT16(s, data.PacketsInSample);
+ Stream_Read_UINT32(s, data.SampleNumber);
+ Stream_Read_UINT32(s, data.cbSample);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, data.cbSample))
+ return ERROR_INVALID_DATA;
+ data.pSample = Stream_Pointer(s);
+
+ /*
+ WLog_DBG(TAG, "videoData: id:%"PRIu8" version:%"PRIu8" flags:0x%"PRIx8" timestamp=%"PRIu64"
+ duration=%"PRIu64 " curPacketIndex:%"PRIu16" packetInSample:%"PRIu16" sampleNumber:%"PRIu32"
+ cbSample:%"PRIu32"", data.PresentationId, data.Version, data.Flags, data.hnsTimestamp,
+ data.hnsDuration, data.CurrentPacketIndex, data.PacketsInSample, data.SampleNumber,
+ data.cbSample);
+ */
+
+ return video_VideoData(context, &data);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT video_control_on_close(IWTSVirtualChannelCallback* pChannelCallback)
+{
+ free(pChannelCallback);
+ return CHANNEL_RC_OK;
+}
+
+static UINT video_data_on_close(IWTSVirtualChannelCallback* pChannelCallback)
+{
+ free(pChannelCallback);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT video_control_on_new_channel_connection(IWTSListenerCallback* listenerCallback,
+ IWTSVirtualChannel* channel, BYTE* Data,
+ BOOL* pbAccept,
+ IWTSVirtualChannelCallback** ppCallback)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = NULL;
+ GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)listenerCallback;
+
+ WINPR_UNUSED(Data);
+ WINPR_UNUSED(pbAccept);
+
+ callback = (GENERIC_CHANNEL_CALLBACK*)calloc(1, sizeof(GENERIC_CHANNEL_CALLBACK));
+ if (!callback)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ callback->iface.OnDataReceived = video_control_on_data_received;
+ callback->iface.OnClose = video_control_on_close;
+ callback->plugin = listener_callback->plugin;
+ callback->channel_mgr = listener_callback->channel_mgr;
+ callback->channel = channel;
+ listener_callback->channel_callback = callback;
+
+ *ppCallback = (IWTSVirtualChannelCallback*)callback;
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT video_data_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
+ IWTSVirtualChannel* pChannel, BYTE* Data,
+ BOOL* pbAccept,
+ IWTSVirtualChannelCallback** ppCallback)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = NULL;
+ GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback;
+
+ WINPR_UNUSED(Data);
+ WINPR_UNUSED(pbAccept);
+
+ callback = (GENERIC_CHANNEL_CALLBACK*)calloc(1, sizeof(GENERIC_CHANNEL_CALLBACK));
+ if (!callback)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ callback->iface.OnDataReceived = video_data_on_data_received;
+ callback->iface.OnClose = video_data_on_close;
+ callback->plugin = listener_callback->plugin;
+ callback->channel_mgr = listener_callback->channel_mgr;
+ callback->channel = pChannel;
+ listener_callback->channel_callback = callback;
+
+ *ppCallback = (IWTSVirtualChannelCallback*)callback;
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT video_plugin_initialize(IWTSPlugin* plugin, IWTSVirtualChannelManager* channelMgr)
+{
+ UINT status = 0;
+ VIDEO_PLUGIN* video = (VIDEO_PLUGIN*)plugin;
+ GENERIC_LISTENER_CALLBACK* callback = NULL;
+
+ if (video->initialized)
+ {
+ WLog_ERR(TAG, "[%s] channel initialized twice, aborting", VIDEO_CONTROL_DVC_CHANNEL_NAME);
+ return ERROR_INVALID_DATA;
+ }
+ video->control_callback = callback =
+ (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK));
+ if (!callback)
+ {
+ WLog_ERR(TAG, "calloc for control callback failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ callback->iface.OnNewChannelConnection = video_control_on_new_channel_connection;
+ callback->plugin = plugin;
+ callback->channel_mgr = channelMgr;
+
+ status = channelMgr->CreateListener(channelMgr, VIDEO_CONTROL_DVC_CHANNEL_NAME, 0,
+ &callback->iface, &(video->controlListener));
+
+ if (status != CHANNEL_RC_OK)
+ return status;
+ video->controlListener->pInterface = video->wtsPlugin.pInterface;
+
+ video->data_callback = callback =
+ (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK));
+ if (!callback)
+ {
+ WLog_ERR(TAG, "calloc for data callback failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ callback->iface.OnNewChannelConnection = video_data_on_new_channel_connection;
+ callback->plugin = plugin;
+ callback->channel_mgr = channelMgr;
+
+ status = channelMgr->CreateListener(channelMgr, VIDEO_DATA_DVC_CHANNEL_NAME, 0,
+ &callback->iface, &(video->dataListener));
+
+ if (status == CHANNEL_RC_OK)
+ video->dataListener->pInterface = video->wtsPlugin.pInterface;
+
+ video->initialized = status == CHANNEL_RC_OK;
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT video_plugin_terminated(IWTSPlugin* pPlugin)
+{
+ VIDEO_PLUGIN* video = (VIDEO_PLUGIN*)pPlugin;
+
+ if (video->control_callback)
+ {
+ IWTSVirtualChannelManager* mgr = video->control_callback->channel_mgr;
+ if (mgr)
+ IFCALL(mgr->DestroyListener, mgr, video->controlListener);
+ }
+ if (video->data_callback)
+ {
+ IWTSVirtualChannelManager* mgr = video->data_callback->channel_mgr;
+ if (mgr)
+ IFCALL(mgr->DestroyListener, mgr, video->dataListener);
+ }
+
+ if (video->context)
+ VideoClientContextPriv_free(video->context->priv);
+
+ free(video->control_callback);
+ free(video->data_callback);
+ free(video->wtsPlugin.pInterface);
+ free(pPlugin);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Channel Client Interface
+ */
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT video_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
+{
+ UINT error = CHANNEL_RC_OK;
+ VIDEO_PLUGIN* videoPlugin = NULL;
+ VideoClientContext* videoContext = NULL;
+ VideoClientContextPriv* priv = NULL;
+
+ videoPlugin = (VIDEO_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, "video");
+ if (!videoPlugin)
+ {
+ videoPlugin = (VIDEO_PLUGIN*)calloc(1, sizeof(VIDEO_PLUGIN));
+ if (!videoPlugin)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ videoPlugin->wtsPlugin.Initialize = video_plugin_initialize;
+ videoPlugin->wtsPlugin.Connected = NULL;
+ videoPlugin->wtsPlugin.Disconnected = NULL;
+ videoPlugin->wtsPlugin.Terminated = video_plugin_terminated;
+
+ videoContext = (VideoClientContext*)calloc(1, sizeof(VideoClientContext));
+ if (!videoContext)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ free(videoPlugin);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ priv = VideoClientContextPriv_new(videoContext);
+ if (!priv)
+ {
+ WLog_ERR(TAG, "VideoClientContextPriv_new failed!");
+ free(videoContext);
+ free(videoPlugin);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ videoContext->handle = (void*)videoPlugin;
+ videoContext->priv = priv;
+ videoContext->timer = video_timer;
+ videoContext->setGeometry = video_client_context_set_geometry;
+
+ videoPlugin->wtsPlugin.pInterface = (void*)videoContext;
+ videoPlugin->context = videoContext;
+
+ error = pEntryPoints->RegisterPlugin(pEntryPoints, "video", &videoPlugin->wtsPlugin);
+ }
+ else
+ {
+ WLog_ERR(TAG, "could not get video Plugin.");
+ return CHANNEL_RC_BAD_CHANNEL;
+ }
+
+ return error;
+}
diff --git a/channels/video/client/video_main.h b/channels/video/client/video_main.h
new file mode 100644
index 0000000..d09efab
--- /dev/null
+++ b/channels/video/client/video_main.h
@@ -0,0 +1,31 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Optimized Remoting Virtual Channel Extension
+ *
+ * Copyright 2017 David Fort <contact@hardening-consulting.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_VIDEO_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_VIDEO_CLIENT_MAIN_H
+
+#include <freerdp/config.h>
+
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+#include <freerdp/addin.h>
+
+#include <freerdp/channels/video.h>
+
+#endif /* FREERDP_CHANNEL_GEOMETRY_CLIENT_MAIN_H */